Writeup summary
- Challenge Info
- TL-DR
- Analysis of the server code
- Bypass Hmac Verification
- Assemble Secrets and Get Flag
Challenge Info
Your APT group scr1pt_k1tt13z breached into a popular enterprise service, but due to inexperience,
you only got the usernames of the administrators of the service, and an encrypted password for the root admin.
However, you learned that the company had a key agreement ceremony at
some point in time, and the administrators keys are all somehow connected to the root admin’s. http://crypto.chal.csaw.io:5005/
Attachment:
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
#!/usr/bin/env python3
import os
import base64
import hashlib
import random
import flask
from gen_db import DATABASE
app = flask.Flask(__name__)
app.secret_key = "dljsaklqk24e21cjn!Ew@@dsa5"
N = int("00ab76f585834c3c2b7b7b2c8a04c66571539fa660d39762e338cd8160589f08e3d223744cb7894ea6b424ebab899983ff61136c8315d9d03aef12bd7c0486184945998ff80c8d3d59dcb0196fb2c37c43d9cbff751a0745b9d796bcc155cfd186a3bb4ff6c43be833ff1322693d8f76418a48a51f43d598d78a642072e9fff533", 16)
g = 2
k = 3
b = random.randint(0, N - 1)
salt = str(random.randint(0, 2**32 - 1))
def gen_seed():
return random.randint(0, N - 1)
def xor_data(binary_data_1, binary_data_2):
return bytes([b1 ^ b2 for b1, b2 in zip(binary_data_1, binary_data_2)])
def modular_pow(base, exponent, modulus):
if modulus == -1:
return 0
result = 1
base %= modulus
while exponent > 0:
if exponent % 2:
result = (result * base) % modulus
exponent >>= 1
base = (base * base) % modulus
return result
def hmac_sha256(key, message):
if len(key) > 64:
key = sha256(key).digest()
if len(key) < 64:
key += b'\x00' * (64 - len(key))
o_key_pad = xor_data(b'\x5c' * 64, key)
i_key_pad = xor_data(b'\x36' * 64, key)
return hashlib.sha256(o_key_pad + hashlib.sha256(i_key_pad + message).digest()).hexdigest()
def hasher(data):
return int(hashlib.sha256(data.encode()).hexdigest(), 16)
app.jinja_env.globals.update(
gen_seed=gen_seed,
modular_pow=modular_pow,
N=N,
)
@app.route("/", methods=["GET", "POST"])
def home():
if flask.request.method == "POST":
username = flask.request.form.get("username")
if username is None:
flask.flash("Error encountered on server-side.")
return flask.redirect(flask.url_for("home"))
hmac = flask.request.form.get("computed")
if (hmac is not None):
return flask.redirect(flask.url_for("dashboard", user=username, hmac=hmac))
try:
pwd = DATABASE[username]
except KeyError:
flask.flash("Cannot find password for username in database")
return flask.redirect(flask.url_for("home"))
try:
A = int(flask.request.form.get("token1"))
except Exception as e:
flask.flash("Error encountered on server-side")
return flask.redirect(flask.url_for("home"))
if A is None:
flask.flash("Error encountered on server-side.")
return flask.redirect(flask.url_for("home"))
if A in [0, N]:
flask.flash("Error encountered on server-side. >:)")
return flask.redirect(flask.url_for("home"))
xH = hasher(salt + str(pwd))
v = modular_pow(g, xH, N)
B = (k * v + modular_pow(g, b, N)) % N
u = hasher(str(A) + str(B))
S = modular_pow(A * modular_pow(v, u, N), b, N)
K = hashlib.sha256(str(S).encode()).digest()
flask.session["server_hmac"] = hmac_sha256(K, salt.encode())
return flask.jsonify(nacl=salt, token2=B)
else:
return flask.render_template("home.html")
@app.route("/dash/<user>", methods=["POST", "GET"])
def dashboard(user):
if "hmac" not in flask.request.args:
flask.flash("Error encountered on server-side.")
return flask.redirect(flask.url_for("home"))
hmac = flask.request.args["hmac"]
servermac = flask.session.get("server_hmac", None)
print(hmac, servermac)
if hmac != servermac:
flask.flash("Incorrect password.")
return flask.redirect(flask.url_for("home"))
pwd = DATABASE[user]
return flask.render_template("dashboard.html", username=user, pwd=pwd)
if __name__ == "__main__":
app.run()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Jere:
Lakisha:
Loraine:
Ingrid:
Orlando:
Berry:
Alton:
Bryan:
Kathryn:
Brigitte:
Dannie:
Jo:
Leslie:
Adrian:
Autumn:
Kellie:
Alphonso:
Joel:
Alissa:
Rubin:
|
1
|
cbc:254dc5ae7bb063ceaf3c2da953386948:08589c6b40ab64c434064ec4be41c9089eefc599603bc7441898c2e8511d03f6
|
TL-DR
we have a webiste http://crypto.chal.csaw.io:5005/
containing a login form where the server authenthicate users and add the hmac to the session, there is a /home
where server verify the hmac and show the user password so as an attacker we have to get all users password and then assemble them with SHAMIR SECRET SHARING SCHEMA to decrypt the flag.
Analysis of the server code
by checking the server code we can say that it is a flask application that has 2 routes:
/
: handle POST request where we can submit the username and password the essential POST parametre are {token1, username }
when we check the source code of the webpage we can see that token1
is an int value constant that is passed when user log in, then it is making some validation on the value of token1
:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
try:
A = int(flask.request.form.get("token1"))
except Exception as e:
flask.flash("Error encountered on server-side")
return flask.redirect(flask.url_for("home"))
if A is None:
flask.flash("Error encountered on server-side.")
return flask.redirect(flask.url_for("home"))
if A in [0, N]:
flask.flash("Error encountered on server-side. >:)")
return flask.redirect(flask.url_for("home"))
|
we can notice one thing that is strange if A in [0, N]:
which mean that A can be any value except 0 and N, then server calculate the hmac based on the value of A and password then save it in the session:
1
2
3
4
5
6
7
|
xH = hasher(salt + str(pwd))
v = modular_pow(g, xH, N)
B = (k * v + modular_pow(g, b, N)) % N
u = hasher(str(A) + str(B))
S = modular_pow(A * modular_pow(v, u, N), b, N)
K = hashlib.sha256(str(S).encode()).digest()
flask.session["server_hmac"] = hmac_sha256(K, salt.encode())
|
and then return the salt
used in the calculation of hmac and return B
/dash/<user>
: it check the hmac
we send with the hmac
saved in the session if its the same it will redirect to dashboard.html
which will show us the password
Bypass Hmac Verification
To bypass the hmac verfication we have to get back to how the hmac is calculated and get back on what we have noticed back then. So what we can controll is the username
and the value of A
.
By looking more deeper in the calculation of the hmac we can notice it is calculation S = modular_pow(A * modular_pow(v, u, N), b, N)
and this value will be used in sha2 hash to get the hmac so if we can know the value of S
we can calculate the hmac locally and bypass it. but how can we get the value of S
.
In the first part we noticed A
is validated in a strange way if A in [0, N]:
this is to make sure S !=0 but we can send A=2*N which will make S=0
and the salt is sent to us we can calculate the hmac locally !!! .let’s scipt it :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
BASE_URL = 'http://crypto.chal.csaw.io:5005/'
for user in users:
s = requests.Session()
data = {
"username": user,
"token1": 2*N,
}
response = s.post(BASE_URL, data=data)
y = json.loads(response.content.decode())
salt = y["nacl"]
S = 0
K = hashlib.sha256(str(S).encode()).digest()
hmac = hmac_sha256(K, salt.encode())
data = {
"computed": hmac,
"username": user,
}
response = s.post(BASE_URL, data=data)
content = response.content.decode()
hash = re.findall(r'<td>(.*)</td>', content)[1]
print(hash)
|
with this script we can get a list of hashes of each user .
Assemble Secrets and Get Flag
Now that we have all the passwords we can see a pattern in the passwords for example here is the first one 1:c4ee528d1e7d1931e512ff263297e25c:128
and in the description they said that the passwords are connected to the root password( the flag ), so we need a way to assemble those passwords, after wasting a lot of time testing out different methods i finnaly found the correct one which is using the SHAMIR SECRET SCHEME And was able to decrypt and get the flag flag{n0t_s0_s3cur3_4ft3r_4ll}
here is what i did to decrypt it :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from Crypto.Protocol.SecretSharing import Shamir
from Crypto.Cipher import AES
from binascii import unhexlify
shares = []
for idx, hash in enumerate(hashes):
print(idx)
shares.append((idx+1, int(hash, 16)))
key = Shamir.combine(shares)
print(key)
IV = unhexlify('254dc5ae7bb063ceaf3c2da953386948')
cipher = AES.new(key, AES.MODE_CBC, IV)
c = unhexlify(
'08589c6b40ab64c434064ec4be41c9089eefc599603bc7441898c2e8511d03f6')
print(cipher.decrypt(c))
|