Saturday, January 18, 2014

Hack You CTF 2014 - Crypto 200 - Hashme - [Team SegFault]

We have access to the source code of a remote service which allows users to register and login.

Register:
[*] Everytime a new login is created, hash of string SALT+login={username}&role=anonymous is computed
[*] Computed hash is appended to login={username}&role=anonymous, encrypted with a key using xor and then encoded using base64
[*] The generated base64 string is our certificate for login

Login:
[*] The user provided certificate is base64 decoded and decrypted using xor
[*] Hash of SALT+login={username}&role=anonymous is computed and checked with the user supplied hash. If it matches, then login is provided
[*] Administrator gets access to flag file

We have to find a way to get administrator access by forging the request. Ok, first we have to find the key used in the remote machine. This can be done by registering a user with long username
client sends: username as "A"*1000 # login={username}&role=anonymous is our known plain text
server sends: XOR(login={username}&role=anonymous + HASH) # cipher text
Using the plain text - cipher text combination, the KEY can be found.
Now we have to find a way to compute valid hash without knowing the SALT. Thanks to my friend who pointed me out the Hash Length Extension attack. The final 32 byte of hash comes from A,B,C and D of used hash algorithm
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476
X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

for i,ch in enumerate(s):
    k, l = ord(ch), i & 0x1f
    A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
    B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
    C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
    D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF

return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))
We can use the HASH(login={username}&role=anonymous) sent by remote service as initial values, then compute the hash values of newly appended string without knowing the SALT. But one value we need to know is the length of SALT. This can be easily bruteforced.
Below is the code for checking login and administrator.
if hashme(SALT + auth_str) == hashsum:  
    data = parse_qs(auth_str, strict_parsing = True)
    print '[+] Welcome, %s!' % data['login'][0]
    if 'administrator' in data['role']:
    flag = open('flag.txt').readline()
    print flag
We need a craft a input as login={username}&role=anonymous&role=administrator so that parse_qs() sets data['role'] to ['anonymous','administrator'] and if 'administrator' in data['role'] is passed. Finally, find the valid hash value for string login={username}&role=anonymous&role=administrator to get the flag. Below is the code used
#!/usr/bin/env python

import base64
from math import sin

def hashme(length, s, state):
    # my secure hash function
    def F(X,Y,Z):
        return ((~X & Z) | (~X & Z)) & 0xFFFFFFFF
    def G(X,Y,Z):
        return ((X & Z) | (~Z & Y)) & 0xFFFFFFFF
    def H(X,Y,Z):
        return (X ^ Y ^ Y) & 0xFFFFFFFF
    def I(X,Y,Z):
        return (Y ^ (~Z | X)) & 0xFFFFFFFF
    def ROL(X,Y):
        return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF

    #A = 0x67452301
    #B = 0xEFCDAB89
    #C = 0x98BADCFE
    #D = 0x10325476
    B = int(hash[:8],16)
    A = int(hash[8:16],16)
    D = int(hash[16:24],16)
    C = int(hash[24:],16)

    X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

    for i,ch in enumerate(s):
        k, l = ord(ch), (i+length) & 0x1f
        A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
        B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
        C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
        D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF

    return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))



login = "A"*1000 
user_data = 'login=%s&role=anonymous' % login

encoded_user_data = "RK5yZMJaRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJiIqUgB0GMZDU+ldcxAh5N2LaUrM/hIw788YPMU42cruOSc3SaRv/lVRTMODTfQl" 

decoded_user_data = base64.b64decode(encoded_user_data)

key = []
for i in range(len(user_data)):
    key.append(ord(decoded_user_data[i]) ^ ord(user_data[i])) 
key = key[:50]  # sizeof key is 50 bytes

# key
# [40, 193, 21, 13, 172, 103, 4, 88, 61, 108, 17, 37, 167, 45, 60, 135, 36, 30, 127, 84, 151, 233, 184, 12, 120, 244, 206, 43, 8, 220, 171, 43, 13, # 242, 11, 224, 171, 222, 11, 23, 81, 42, 147, 91, 199, 101, 96, 124, 245, 229]

login = "A"
user_data = 'login=%s&role=anonymous' % login
encoded_user_data = "RK5yZMJaRX5PA31AmkxS6EpnEjvimo81G82rT2q5n0k9ym/Tme47IDcT92z2UVJOxNEe+CE5"
decoded_user_data = base64.b64decode(encoded_user_data)

decrypted_data = ''

for i in range(len(decoded_user_data)):
    decrypted_data += chr(ord(decoded_user_data[i]) ^ key[i % len(key)])

target = "&role=administrator"
hash = decrypted_data[-32:]

length_of_salt = 0
length_of_user_data = len(user_data)

for length_of_salt in range(1,30):
    forged_hash = hashme(length_of_salt + length_of_user_data, target, hash)
    string_to_enc = user_data + target + forged_hash
    enc_payload = ''

    for i in range(len(string_to_enc)):
        enc_payload += chr(ord(string_to_enc[i]) ^ key[i % len(key)])

    print "%d : %s" % (length_of_salt, base64.b64encode(enc_payload))
We get successful login and flag, when length of SALT is 20 bytes.
RK5yZMJaRX5PA31AmkxS6EpnEjvimp5+F5irFmm4xkJjm3iU2b9/eCNM8TimAFcdwYca8CU8nQI8OVxbdRefTgzjFi0bMaPejw==
[+] Welcome, A!
CTF{40712b12d4be002e20f51424309a068c}

No comments :

Post a Comment