The Trail of Bits cryptographic services team contributed two cryptography CTF challenges to the recent CSAW CTF . Today we’re going to cover the easier one, titled “Disastrous Security Apparatus Good luck, ‘k?”
This problem involves the Digital Signature Algorithm (DSA) and the way an apparently secure algorithm can be made entirely insecure through surprising implementation quirks. The challenge relies on two bugs, one of which was the source of the Playstation 3 firmware hack , while the other is a common source of security vulnerabilities across countless software products. Despite both of these issues having been known for many years a large number of software developers (and even security engineers) are unfamiliar with them.
If you’re interested in solving the challenge yourself get the code here and host it locally. Otherwise, read on so you can learn to spot these sorts of problems in code you write or review.Flags need capturing
Participants were given the source code ( main.py ) and an active HTTP server they could contact. This server was designed to look roughly like an online signing server. It had an endpoint that signed payloads sent to it and a partially implemented login system with password reset functionality.
The enumerated set of routes:/public_key , which returned a DSA public key’s elements (p, q, g, y) as integers encoded in a JSON structure. /sign/ , which performed a SHA1 hash of the data passed, then signed the resulting hash with the DSA private key and returned two integers (r, s) in a JSON structure. /forgotpass , which generated a URL for resetting a user’s password using random.getrandbits . /resetpass , an unimplemented endpoint that returned a 500 if called. /challenge , returned a valid Fernet token . /capture , which, when presented with a valid DSA signature for a valid Fernet token, yielded the flag.
To capture the flag we’ll need to recover the DSA private key and use that to sign an encrypted payload from the /challenge endpoint. We then submit both the challenge value and the signature to /capture . This allows the server to verify you’ve recovered the private key. Let’s go!DSA signing, the Disastrous Security Apparatus in actio
A complete DSA key is made up of 5 values: p , q , g , x , and y .
p , q , g , and y are all public values. The /public_key endpoint on the server gives these values and can be used to verify that a given signature is valid. The private value, x , is what we need. A DSA signature is normally computed as followsFirst pick a k where 0 < k < q Compute the value r . Conceptually this is g k mod p mod q. However, as g and k are both large numbers it is very slow to compute this value directly. Fortunately modular exponentiation completes the calculation very quickly. In python you can calculate this via the built-in pow method: pow(g, k, p) % q . Calculate the modular multiplicative inverse of k modulo q . That is, kinv such that (k * kinv) % q = 1 Compute the hash of the message you want to sign. This particular code uses SHA1 and then converts the byte string into a big endian integer. To do this in Python: int.from_bytes(hashlib.sha1(data).digest(), 'big') (Python 3 required!) Finally, calculate s using kinv * (h + r * x) % q
The signer implementation in main.py conveniently possesses this exact codedef sign(ctf_key: DSAPrivateKeyWithSerialization, data: bytes) -> tuple(int, int):
data = data.encode("ascii")
pn = ctf_key.private_numbers()
g = pn.public_numbers.parameter_numbers.g
q = pn.public_numbers.parameter_numbers.q
p = pn.public_numbers.parameter_numbers.p
x = pn.x
k = random.randrange(2, q)
kinv = _modinv(k, q)
r = pow(g, k, p) % q
h = hashlib.sha1(data).digest()
h = int.from_bytes(h, "big")
s = kinv * (h + r * x) % q
return (r, s)
To confirm that r and s are correct you can also perform a DSA verification.Compute w , the modular inverse of s modulo q Calculate u1 = (h * w) % q Calculate u2 = (r * w) % q Calculate v , defined as ((g ** u1) * (y ** u2)) % p % q . This will need to be done via modular exponentiation!
At this point v should be equal to r .Tricksy math, ruining our security
We’ve seen the math involved in generating and verifying a DSA signature, but we really want to use the set of values we know to recover a value we do not ( x , the private scalar). Recall this equation?
s = (kinv * (h + r * x)) % q
A DSA signature is composed of two values: r and s . We also know h is the value that is being signed and with a signing oracle we pick that value. Finally, we know q as that is part of the public key that is used to verify a DSA signature. This leaves us with two unknowns: kinv and x . Let’s solve for x :s = (kinv * (h + r * x)) % q s * k = (h + r * x) % q (s * k) % q = (h + r * x) % q Note: (s * k) will always be less than q, so adding % q is just for clarity. ((s * k) - h) % q = (r * x) % q (rinv * ((s * k) - h)) % q = x
rinv is calculated just like kinv (the modular multiplicative inverse ).
As you can see from the final equation, if we can determine the k used for any given signature tuple (r, s) then we can recover the private scalar. But k is generated via random.randrange so it’s not predictable.RNGs and global state oh my! Random number generation is