HITCON CTF Quals 2015 CTFTIME Page. Most of the challenges were very tedious, and this is one of the challenges that we solved (Although we only managed to solve this after the CTF ended).
Simple
Points: 100
Category: Cryptography
Description Become admin!
http://52.69.244.164:51913
simple-01018f60e497b8180d6c92237e2b3a67.rb
md5: 4bd00c892d5e71f6d1d25d0bff2f49ec
Our solution
Given the source code of the website, we’re told to get admin. Looking at the source code provided, to be able to print the flag out, we have to get the conditon r[‘admin’] to be equal to true.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env ruby
require 'sinatra/base'
require 'sinatra/cookies'
require 'openssl'
require 'json'
KEY = IO.binread('super-secret-key')
FLAG = IO.read('/home/simple/flag').strip
class SimpleApp < Sinatra::Base
helpers Sinatra::Cookies
get '/' do
auth = cookies[:auth]
if auth
begin
auth = auth.b
c = OpenSSL::Cipher.new('AES-128-CFB')
c.decrypt
c.key = KEY
c.iv = auth[0...16]
json = c.update(auth[16..-1]) + c.final
r = JSON.parse(json)
if r['admin'] == true
"You're admin! The flag is #{FLAG}"
else
"Hi #{r['username']}, try to get admin?"
end
rescue StandardError
'Something wrong QQ'
end
else
<<-EOS
<html><body><form action='/' method='POST'>
<input type='text' name='username'/>
<input type='password' name='password'/>
<button type='submit'>register!</button>
</form></body></html>
EOS
end
end
post '/' do
username = params['username']
password = params['password']
if username && password
data = {
username: username,
password: password,
db: 'hitcon-ctf'
}
c = OpenSSL::Cipher.new('AES-128-CFB')
c.encrypt
c.key = KEY
iv = c.random_iv
json = JSON.dump(data)
enc = c.update(json) + c.final
cookies[:auth] = iv + enc
redirect to('/')
else
'Invalid input!'
end
end
end
It seems that the IV used as well as the encrypted json is kept in the client’s cookie, and that the same cookie is used to determine if you’re an admin. (This indicates that if we can spoof the encrypted json, we can become admin)
AES-128 in CFB mode has a block size of 16 bytes.
Simply put,
Ciphertext of block #1 = E(IV, key) ^ Plaintext
Therefore, with knowledge of plaintext and ciphertext, we are able to obtain E(IV, key) and to forge for the first block of cipher text.
With a username and password of b, the Plaintext of the first block will be
{"username":"b",
and we’ll use that knowledge to obtain our E(IV, key)
This is our exploit script that forges our first block to be:
{"admin": true }
and allows us to obtain our flag!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import requests
import urllib
def main():
original_cookie = "\xE9a\x89\xEC\xC7\x7C\xBC\x15\x92\xAD\xF8\x17\xF8\x40" \
"wV\xAB524\xF2\xF5UA\xE8\x1A\x29\xD4\xCB\xFA\xF6\xB3" \
"\x95h\x2B\x0D\xF4\xB9\xC8\xDB\xF8n\xB9o\xBES\x11d\xA3" \
"9\xA3c\x3Fi\xE7\xFA\x1C\xD0\xDBk\xDD\xD2_6\x06"
original_cookie = original_cookie.encode('hex')
iv = original_cookie[0:32]
first_16_byte_block = original_cookie[32:64]
print "IV: %s" % iv
print "First Block: %s" % first_16_byte_block
#Plain text of first 16 byte block.
plaintext ='{"username":"b",'
encrypted_iv = int(plaintext.encode('hex'),16) ^ int(first_16_byte_block,16)
encrypted_iv = hex(encrypted_iv)[2:-1]
print "Encrypted IV: %s" % encrypted_iv
#The text I want to forge in the first block.
forge_text = '{"admin": true }'
print 'Encrypting payload...'
payload = int(forge_text.encode('hex'),16) ^ int(encrypted_iv,16)
payload = hex(payload)[2:-1]
payload = iv + payload
print "PAYLOAD: %s" % payload
cookie = {"auth": payload.decode("hex")}
r = requests.get("http://52.69.244.164:51913/", cookies=cookie)
print "Flag: %s" % r.text
if __name__ == "__main__":
main()
Running the script gives us:
And we have our flag: hitcon{WoW_CFB_m0dE_5o_eAsY}
comments powered by Disqus