HackTheBox Signing Factory Challenge
https://app.hackthebox.com/challenges/641
Description
After studying about vulnerabilities on signing servers, a group of researchers gathered one night and comitted into creating a modern and more secure way of signing tokens for authentication. They are certain that their product is ready for distribution and want to do a last security audit before publicizing their work. They provided you with access to the server. Is their way of signing messages the solution to all previous attacks?
Exploitation
#!/usr/bin/python3
from base64 import b64decode, b64encode
from pwn import *
from sympy import mod_inverse, factorint
import hashlib,string,random,re
golden_ratio = 2654435761
admin = "System_Administrator"
def get_process():
try:
host, port = sys.argv[1].split(':')
return remote(host, int(port))
except IndexError:
print(f'Usage: python {sys.argv[0]} <ip:port>')
exit(1)
def extract_base64_token(response):
match = re.search(r"Your session token is b'([A-Za-z0-9+/=]+)'", response)
if match:
return match.group(1)
else:
raise ValueError("Failed to extract Base64 token from response")
def parse_e_n_from_response(response):
match = re.search(r"\(e,N\) = \((\d+),\s*(\d+)\)", response)
if match:
return int(match.group(1)), int(match.group(2))
else:
raise ValueError("Failed to parse e and n from response")
def calculate_h_n_from_equations(equations):
equation_re = re.compile(r"equation\(unknown, (\d+), (\d+)\) = (\d+)")
unknowns = []
for match in equation_re.finditer(equations):
rnd = int(match.group(1))
equation_output = int(match.group(3))
inverse_rnd = mod_inverse(rnd, golden_ratio)
unknown = (equation_output * inverse_rnd) % golden_ratio
unknowns.append(unknown)
h_n = 1
for unknown in unknowns:
h_n *= unknown
return h_n
def hash_username(username):
numeric_username = int(username.encode().hex(), 16)
h = hash_var(numeric_username)
return int(h)
def hash_var(key):
return (((key % golden_ratio) * golden_ratio) >> 32)
def reverse_hash_var(hashed):
shifted_value = hashed << 32
for candidate in range(golden_ratio):
if (((candidate % golden_ratio) * golden_ratio) >> 32) == hashed:
return candidate
return None
def numeric_to_username(numeric_username):
hex_string = format(numeric_username, "x")
if len(hex_string) % 2 != 0:
hex_string = "0" + hex_string
return bytes.fromhex(hex_string).decode("utf-8")
def reverse_hash_username(hashed_value):
numeric_username = reverse_hash_var(hashed_value)
return numeric_to_username(numeric_username)
def get_auth_token(username, d, n):
numeric_username = int(username.encode().hex(), 16)
h = hash_var(numeric_username)
auth = pow(int(h), d, n)
return b64encode(str(auth).encode()).decode()
def get_signature_and_public_key(p, plaintext):
p.sendline(b"0")
p.recvuntil(b"Enter a username: ")
p.sendline(plaintext.encode())
response = p.recvuntil(b"[+] Option >> ").decode()
token_base64 = extract_base64_token(response)
p.sendline(b"2")
equations = p.recvuntil(b"Enter the hash(N): ").decode()
h_n = calculate_h_n_from_equations(equations)
p.sendline(str(h_n).encode())
response = p.recv().decode()
e, n = parse_e_n_from_response(response)
auth = int(b64decode(token_base64).decode())
return auth, e, n
def get_flag(p, auth_token):
p.sendline(b"1")
p.recvuntil(b"Enter your username: ")
p.sendline(admin.encode())
p.recvuntil(b"Enter your authentication token: ")
p.sendline(auth_token.encode())
response = p.recvline().decode()
return response
def prepare_auth_token(forged_signature):
auth_token = b64encode(str(forged_signature).encode()).decode()
return auth_token
def is_alphanumeric_latin(s):
latin_alphanumeric = set(string.ascii_letters + string.digits)
return all(char in latin_alphanumeric for char in s)
def main():
p = get_process()
factors = factorint(hash_username(admin))
ciphers = []
for factor in factors:
i = 1
numeric_username = reverse_hash_var(factor)
while True:
try:
uname = numeric_to_username(numeric_username)
if not is_alphanumeric_latin(uname):
raise
break
except:
numeric_username = numeric_username + (golden_ratio * i)
i += 1
cipher, e, n = get_signature_and_public_key(p, uname)
ciphers.append(cipher)
forged_signature = 1
for cipher in ciphers:
forged_signature = (forged_signature * cipher) % n
auth_token = prepare_auth_token(forged_signature)
response = get_flag(p, auth_token)
print(f"Server response: {response}")
p.close()
if __name__ == "__main__":
main()
Summary
Signing Factory: reconstruct the PRNG state from the leak, replay it, and recover the flag.