Secure Remote Password

Aperi'CTF 2019 - Cryptography (175 pts).

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