Aperi’CTF 2019 - Secure Remote Password
Challenge details
Event | Challenge | Category | Points | Solves |
---|---|---|---|---|
Aperi’CTF 2019 | Secure Remote Password | Cryptography | 175 | 2 |
Comme projet pendant ses cours de cryptographie votre fils a développé un programme qui simule un échange du protocole SRP. Cependant, il a tendance à se croire meilleur que les autres et se permet de modifier certaines choses pour des raisons de “performance”.
En tant que cryptanalyste, vous lui avez fait remarquer que son implémentation est cassée dans le cas d’une attaque du type Homme du milieu, mais il ne vous croit pas. Prouvez-lui qu’il a tord !
nc srp.aperictf.fr 9898
PS: Le brute-force du service n’est pas autorisé.
Fichier : un programme - md5sum: b3725e4cce5aeec4071a5e92d88b862f
TL;DR
SRP protocole with a bad implementation lead to insecure crypto.
Methodology
After a quick google search we find that the code is mostly a copy and paste from the example on Wikipedia.
The only différence is in the calculation of the public ephemeral value B :
B = k * v + pow(g, b, N)
Instead of :
B = (k * v + pow(g, b, N)) % N
It may not seem like much but like explained in this good blog post, it can leak the first 256 bytes of v
, the Password verifier.
This can be achived simply by dividing B
with k
.
Having the knowledge of the password verifier (at least the first few bytes) allows you to brute-force the password until you find one that is compatible with the leaked verifier :
x = H(known_salt, known_username, password_guess)
v = pow(known_g, x, known_N)
if hex(v)[:60] == partial_v:
#WIN
Because the password is only 3 characters long, this can be achieved in a matter of seconds.
Full script available here
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# https://www.computest.nl/nl/knowledge-platform/blog/exploiting-two-buggy-srp-implementations/
from pwn import *
import itertools
def H(*args): # a one-way hash function
a = ':'.join(str(a) for a in args)
return int(hashlib.sha256(a.encode('utf-8')).hexdigest(), 16)
N = 0xc037c37588b4329887e61c2da3324b1ba4b81a63f9748fed2d8a410c2fc21b1232f0d3bfa024276cfd88448197aae486a63bfca7b8bf7754dfb327c7201f6fd17fd7fd74158bd31ce772c9f5f8ab584548a99a759b5a2c0532162b7b6218e8f142bce2c30d7784689a483e095e701618437913a8c39c3dd0d4ca3c500b885fe3
g = 0x2
k = 0xb317ec553cb1a52201d79b7c12d4b665d0dc234fdbfd5a06894c1a194f818c4a
conn = remote("srp.aperictf.fr", 9898)
conn.recvuntil("1. client sends username I and public ephemeral value A to the server")
conn.recvuntil("I = ")
username = conn.recv(10)
conn.recvuntil("s = 0x")
salt = int(conn.recv(16), 16)
conn.recvuntil("B = 0x")
B = int(conn.recvline().strip(), 16)
partial_v = hex(B/k)[:60]
conn.recvuntil("What is the password ?")
found = ""
l = log.progress("Searching password...")
for p in itertools.product(string.ascii_lowercase + string.ascii_uppercase + string.digits, repeat=3):
password = ''.join(p)
x = H(salt, username, password)
v = pow(g, x, N)
if hex(v)[:60] == partial_v:
found = password
break
l.success("Found password : {}".format(found))
conn.sendline(found)
print(conn.recvlines(3)[2])
conn.close()
Output:
[+] Opening connection to srp.aperictf.fr on port 9898: Done
[+] Searching password...: Found password : FcP
APRK{N0t_s0_s3cUr3_4nyM0r3}
[*] Closed connection to srp.aperictf.fr port 9898
Flag
APRK{N0t_s0_s3cUr3_4nyM0r3}
ENOENT