In this level we are said that our challange is login with administrator role in a service listening on hackyou2014tasks.ctf.su 7777 We are given the following source code:
#!/usr/bin/python
from math import sin
from urlparse import parse_qs
from base64 import b64encode
from base64 import b64decode
from re import match
SALT = ''
USERS = set()
KEY = ''.decode('hex')
def xor(a, b):
return ''.join(map(lambda x : chr(ord(x[0]) ^ ord(x[1])), zip(a, b * 100)))
def hashme(s):
#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
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]))
def gen_cert(login):
global SALT, KEY
s = 'login=%s&role=anonymous' % login
s += hashme(SALT + s)
print("decrypted cert: %s" % s)
s = b64encode(xor(s, KEY))
print("encrypted cert: %s" % s)
return s
def register():
global USERS
login = raw_input('Your login: ').strip()
if not match('^[\w]+$', login):
print '[-] Wrong login'
return
if login in USERS:
print '[-] Username already exists'
else:
USERS.add(login)
print '[+] OK\nYour auth certificate:\n%s' % gen_cert(login)
def auth():
global SALT, KEY
cert = raw_input('Provide your certificate:\n').strip()
try:
cert = xor(b64decode(cert), KEY)
print cert
auth_str, hashsum = cert[0:-32], cert[-32:]
print auth_str
print hashsum
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
else:
print '[-] Auth failed'
except:
print '[-] Error'
def start():
while True:
print '======================'
print '[0] Register'
print '[1] Login'
print '======================'
num = raw_input().strip()
if num == '0':
register()
elif num == '1':
auth()
start()
The service generates certificate when you register that you need to present in order to login in. The certificate is a XOR encrypted version of the following string:
login=<login>&role=anonymous<salted hash of login+role string>
The problem is that we dont know the encryption key nor the hash salt. So let’s take it one step at a time:
Getting the key to the kingdom
Getting the key was the easy part as the cert is encrypted in an ECB way, we only need to send a login name long enough so that the whole key is xored with our know long login name, so we register the user:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
And get the cert:
RK5yZMJaRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pA6zemHJWmU2UgJoSMhYT7cXe0kyoN7cakrNq0lu7MgSaJYz0p/rb3NpE6FqpgZQ
Now if we xor the two together (adding “login=” before the login name) we get the key and since our login name was long enough we can extract the key that is repeated several times:
28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5
Now we can decrypt our cert and extrack the login string and hashsum:
[+] Credentials: login=pwntester&role=anonymous
[+] Hashsum: 3e4d482fd5ce578af79312466b50b8f6
Putting some salt
Our goal is to submit an “administrator” version of the string so we need to know the salt in order to produce the right hash that is going to be checked in the server … or not? Well, actually, the hashing function is not reversible and no collisions are found easy, but there is still hope in the way of Length extension attacks. Actually is even simpler since we dont have to care about the padding! Ok, so here is the idea.
- The Hashing state machine starts in a initial state (that we know, check A,B,C,D in the hashme function)
- The hashing machine iterates over all the characters (abcd) and ends in a different state that is returned as the hashsum
- If we extend the original characters (abcd1234) and pass it to the hash function, we can do two things:
- Start from scratch, reset the hash FSM, and calculate process it till there are no more characters and we return the last state in the form of a hashsum
- Since we already hashed some characters and know the machine state, we can modify the hash FSM so its initial state is the one returned when we hashed (abcd) and then just continue from that state with the new characters (1234) until there are no more characters and we return the state in the form of a hashsum
Well, the server is going to do the first approach, but we can do the second without knowing the Salt!! So we know that “login=pwntester&role=anonymous” hash is 3e4d482fd5ce578af79312466b50b8f6.
Lets say we want to calculate the hash of “login=pwntester&role=anonymousNEWSTUFFHERE”, we can reset the Hash machine so its initial state is 3e4d482fd5ce578af79312466b50b8f6 and then just hash the “NEWSTUFFHERE”, the result will be the same hash as hashing the whole string.
Now, if we focus on the auth() method:
def auth():
global SALT, KEY
cert = raw_input('Provide your certificate:\n').strip()
try:
cert = xor(b64decode(cert), KEY)
print cert
auth_str, hashsum = cert[0:-32], cert[-32:]
print auth_str
print hashsum
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
else:
print '[-] Auth failed'
except:
print '[-] Error'
We can see that the auth string is parsed as a query string (parse_qs) so if we pass different parameters with the same name, they will be treated as an array. Then the “if ‘administrator’ in data[‘role’]” will pass if one of them is administrator
So now we know what we need to hash:
login=pwntester&role=anonymous&role=administrator
This is the function I wrote to hash from a given state:
def hashmeFromState(s,hash,init):
#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
B = int(hash[0:8], 16)
A = int(hash[8:16], 16)
D = int(hash[16:24], 16)
C = int(hash[24:32], 16)
X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]
i = init
for j,ch in enumerate(s):
# We add the length of the previous state (we dont know secret length so we have to brute force it) to restaurate the state
k, l = ord(ch), i & 0x1f
if j==0:
print("hashmeext pos:{0} char:{1} l:{2}".format(j,ch,l))
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
i += 1
return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))
Note that we dont know the length of the Salt, so we need to brute force it to initialize the hash FST in the right state. After running the script against the live service, we get that the right length is 18:
alvaro@winterfell ~/D/h/crypto200> python crack.py
[+] Concatenated key (250 bytes): 28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5
[+] Key: 28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5
[+] Credentials: login=pwntester&role=anonymous
[+] Hashsum: 3e4d482fd5ce578af79312466b50b8f6
[+] User Credentials: login=pwntester&role=anonymous3e4d482fd5ce578af79312466b50b8f6
[+] User Cert: RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vmE45lj/YmbhvIjJPpmz/BAZLzNYZ8yE7mgUxaF9UdxM=
[-] Auth failed
hashmeext pos:0 char:& l:1
[+] Admin Credentials (secret length=1: login=pwntester&role=anonymous&role=administrator72d2e3d8de7b390f146cc6b5e8552ea8)
[+] Admin Cert (secret length=1: RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9IapSdonwM8PFhbcxaeHVq2ECgcN6GLjWlAwfsZbb2T)
[+] Admin Cert decoded (secret length=1: login=pwntester&role=anonymous&role=administrator72d2e3d8de7b390f146cc6b5e8552ea8)
...
...
...
[-] Auth failed
hashmeext pos:0 char:& l:18
[+] Admin Credentials (secret length=18: login=pwntester&role=anonymous&role=administrator6ca059630c51cb32e3d791aeca560eae)
[+] Admin Cert (secret length=18: RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9NLoCU4lVE3aF5ZIEbFHg7iF3pIbaaI3W8Zwfgbbb3O)
[+] Admin Cert decoded (secret length=18: login=pwntester&role=anonymous&role=administrator6ca059630c51cb32e3d791aeca560eae)
[+] Welcome
Eureka!!
Now we can use the cert to login and get the flag:
RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9NLoCU4lVE3aF5ZIEbFHg7iF3pIbaaI3W8Zwfgbbb3O
alvaro@winterfell ~/D/h/crypto200> nc hackyou2014tasks.ctf.su 7777 1
======================
[0] Register
[1] Login
======================
1
Provide your certificate:
RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9NLoCU4lVE3aF5ZIEbFHg7iF3pIbaaI3W8Zwfgbbb3O
[+] Welcome, pwntester!
CTF{40712b12d4be002e20f51424309a068c}