Tetcon is one of the biggest security conferences in Viet Nam. There are various talks which speak both in Vietnamese and English. In this year, the first time, organizers decided to host a hacking challenge – Capture The Flag (CTF) !
While CTF was running, I solved 3 tasks, such as: getit, next and “Who let the dog out?”.
First two tasks is not quite hard. You should try it yourself. In this post, I would like to talk about “Who let the dog out?”. It’s about cryptography attack. In particular, it is the POODLE attack. And the author of this task is Thai Duong (thaidn), one of experts who find out this attack.
Task description:
Source: http://pastebin.com/Mr6PJBwn (my mirror: https://l4w.io/files/CTFs/tetcon-2015/crypto200.py)
Server: http://crypto-class.appspot.com/tetcon
Look at the source-code:
1 2 3 4 5 6 7 8 9 |
def get(self): p1 = str(self.request.get('p1').strip()) p2 = str(self.request.get('p2').strip()) if p1 and p2: self.response.clear() self.response.set_status(200) self.response.out.write(Oracle(KEY).encrypt(p1 + FLAG + p2).encode('hex')) else: self.post() |
So we could choose prefix and suffix of plaintext.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def post(self): c = self.request.get('c').strip().decode('hex') po = Oracle(KEY) try: m = po.decrypt(c) if FLAG in m: self.response.clear() self.response.set_status(200) return else: self.error(404) return except: self.error(403) return |
We passes ciphertext as GET paramater c
If any errors or exceptions occur, the server will return 403.
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 |
def random_pad(data, bs=16): last = bs - (len(data) % bs) - 1 data += os.urandom(last) data += chr(last) return data def random_unpad(data, bs=16): pad_length = ord(data[-1]) + 1 return data[0:-pad_length] ... def encrypt(self, msg): iv = random_block(self.bs) aes = AES.new(self.key, 2, iv) # MODE_CBC mac = hmac.new(self.key) mac.update(iv) mac.update(msg) p = random_pad(msg + mac.digest()) return iv + aes.encrypt(p) def decrypt(self, ciphertext): iv = ciphertext[0:self.bs] ciphertext = ciphertext[self.bs:] aes = AES.new(self.key, 2, iv) # MODE_CBC mac = hmac.new(self.key) p = random_unpad(aes.decrypt(ciphertext)) msg = p[:-16] digest = p[-16:] mac.update(iv) mac.update(msg) if mac.digest() != digest: raise Exception return msg |
The input data which passes to encrypt
function will be encrypted with AES-CBC MODE. Moreover, it’s using HMAC.
We knew what block_size = 16, FLAG size = 32, hmac size = 16.
curl "http://crypto-class.appspot.com/tetcon?p1=AAAAAAAAAAAAAAAA&p2=AAAAAAAAAAAAAAAA"
0acbad9b05fbc03551afdf147446a49b88754146ca526818f5d61a14b4b3fc5d73e12492fbf42c7d326d21a5329dc673ddae3c3ca20d8509dc899f3e3a50a43d2983f99e0b6b0e28ee3270f42b9ce770c74bf206eb25e67371b2dab91af52fc34fc8f00f557a19299e203e49618e3a90
Since HMAC checks integrity of the message (iv + msg), we couldn’t modify or inject anything. And if HMAC isn’t same as result derives from its calculation, code will throw exception and the server will return 403 as I mentioned earlier.
The vulnerability might be in somewhere else. Dig more.
You could see that the the padding function random_pad
is not using PKCS7 method or something like that.
No matter what the content of the padding bytes is, as long as the last byte must specify padding size.
What if we do following steps:
- Append a block FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF{XX} before padding block with {XX} is a byte we need to bruteforce (why? wait a bit, i will tell you).
- Replace padding block into BLOCK[0] (I mean AAA… block, because IV isn’t encrypted).
Ok, Let’s think about it.
What will happen ?
88754146ca526818f5d61a14b4b3fc5d will be decrypt as padding block. This is how AES CBC MODE decrypt our data.
More details:
(yeah, my paint skill is noob, i know :D)
Then, we bruteforce XX with a byte in range from 0x1 to 0xFF. Thus, with an average of 256 tries to bruteforce with XX, there is once time, you will be able to getHTTP status code 200. How come ? (In this case, XX was 0xC5)
As we discussed earlier, If you try to modify-inject data, it will fail and throw exception. But, assume that last byte which specifies padding-size is 31. random_unpad
function will return data[0:-31]. It means that the block which we appended to encrypted data will be skipped!.
Ok. Get back to our situation, XX was 0xC5 and the server responsesHTTP status code 200. We can tell that padding-size was 31. The block ffffffffffffffffffffffffffffffc5
was skipped.
Well, then what could we do with that ?.
Remember that 88754146ca526818f5d61a14b4b3fc5d
was decrypted as padding block so 0x5d will be decrypted then XOR with 0xC5 = 31 ?! right ?
=> The last byte of decrypted data is 0xDA (0xC5 ^ 31)
Yeah, If you understand what i just said, then you can know what is P[0][15] ? (P=Plaintext).
P[0] = Decrypt(88754146ca526818f5d61a14b4b3fc5d)
Since this block is the first cipher block
C[0]
so we use IV
to XOR.
IV = 0acbad9b05fbc03551afdf147446a49b
=> P[0][15] = Decrypt(C[0])[15] ^ IV[15] = 0xDA ^ 0x9B = 0x41
chr(0x41) => A
Replace 88754146ca526818f5d61a14b4b3fc5d
into C[1] (16 bytes FLAG encrypted), and do it again, we would get 16th byte of FLAG.
Since we could choose prefix-suffix of plaintext. we probably leak the whole plaintext of encrypted data by adjusting the block.
WE GOT IT!
https://l4w.io/files/CTFs/tetcon-2015/crypto200_sol.py
1 t t
1 e te
1 t tet
1 c tetc
1 o tetco
1 n tetcon
1 { tetcon{
1 { tetcon{{
1 p tetcon{{p
1 o tetcon{{po
1 o tetcon{{poo
1 d tetcon{{pood
1 l tetcon{{poodl
1 e tetcon{{poodle
1 tetcon{{poodle
1 v tetcon{{poodle v
2 s tetcon{{poodle vs
2 tetcon{{poodle vs
2 s tetcon{{poodle vs s
2 s tetcon{{poodle vs ss
2 l tetcon{{poodle vs ssl
2 v tetcon{{poodle vs sslv
2 3 tetcon{{poodle vs sslv3
2 : tetcon{{poodle vs sslv3:
2 tetcon{{poodle vs sslv3:
2 1 tetcon{{poodle vs sslv3: 1
2 tetcon{{poodle vs sslv3: 1
2 – tetcon{{poodle vs sslv3: 1 –
2 tetcon{{poodle vs sslv3: 1 –
2 0 tetcon{{poodle vs sslv3: 1 – 0
2 } tetcon{{poodle vs sslv3: 1 – 0}
2 } tetcon{{poodle vs sslv3: 1 – 0}}
Flag: tetcon{{poodle vs sslv3: 1 – 0}}
Yeah, and this was how the POODLE attack SSLv3 ;). You can read it further more at:
Thank you for your reading. Sorry for my english. I’m not good at it :(.
Shout-out to NN, my friends, my bros who always support me :).
I’ve finished as winner 1st, and got ~$940 prize :D.
amazing! this post is really helpful to understand the poodle attack
Good Writeup !
Hay!
Thank you for this.
MAHNLUAT Great Job 🙂 , Congratulation
Good writeup!
[…] là attack bằng POODLE. Để đọc thêm về dạng bài này, tham khảo ở blog của anh @l4w tại đây. Nhưng khi mình test, tác giả để nguyên key = ‘xff’ * 16. Nên […]
[…] https://l4w.io/2015/01/tetcon-ctf-2015-crypto200-the-poodle-attack/ […]