17 – Zombieshop
A new company offers a lot of anti zombie equipment to protect yourself. Unfortunately not everyone can buy the good stuff. Only privileged users may do so. We managed to create an account, but it is not privileged. Your mission is to buy “Anti zombie Spray”.
zomboy53:killthezombies
https://ctf.fluxfingers.net:2077/
Summary: bruteforce DES
Here you can add things to basket and order them. After some analysis it’s easy to notice an interesting cookie “data”:
%60%C8%9D%9B%AD%A4%8D%84%8Fs%DF%12cF%CDNQQ%16%F7%0B%C8E%CF%18%A6%DA%B0%3D%B8%BA%1D%B5U%DBq%DF%F8%3B%2F%BC%D6%8D%3D%C0%F3%24W.%EE%C7a4%A6%0C%08%F81%09%AF%C5%07%8B%19%A8%FF%9F%95%27%07%CCN%B1%B6w%AA%01%CA%A0i%D8%0Aj%A0RI%9F3%2F%60%B8%94%10%05%9D%8F7%22%9B%95%13%19%FB%BD%BB%88%83%E4%DDEL%D1%84%9D%DBX%B8-y%AE%9DS%CB+C%60%FE%81%7C%5B%A9%7EH%B0.%3B_f%C0+%BC%D8%FF%BCN%0A%3D%FBaX%0D%134%ED%C5%DE%22L%CDt%9B%D9%FC%90%22_b%83%94%3F%5C%2330%8C%E5%AF%D6%F1%C4%F1%CC%FA%8D%DCW%9A6%E7O%A3%12%80%CD%C3Gpm%ED%07%93%26%A2%CE%A0%D6x%19%E5%F5%81%8E%E2.%DE%92%14%FE%89%B3%5E%C6%98%9A%AF%02%1F%84%E7%86%B2%DF%B9%07%CB%23h%E5%12%0DWM%92%AE%13aU%16%F1L%AA%8E%02%12%DF%97%88%60%CD%84c%EA%F4%0B%AF%C9%88%26%12%F8%BF%EC0%A2%F6%81-%F8%92%04p.%AF%14T%2B%F9%DE%CC%F8G%B4%21%23%04id%F9%E2%B2%28%BE%FB%2C%F3%81AQ%DD%BFW%08%11%B8%02%90%A7%9B%DA%A5%12h%95%C2%05%8F%24%9B%DB%F9%23%A2%B8%CD%AC%9CG3%23%CB%F2dU%2Fc%3E%9C%0F%3A%EE%F7%B8%BB%F72%CAviL%BE%21%E9%FAD%F1%1B%AF%B3%F6%3A4%A2%E1%B7%0E+%DD%CC%EFZ%04%A2x%D0%DB6%F0%94%1C-%A9%7E%18%D2%1E%15%F4%F8%07%40%E4%3B%BD%10d%EAp%26%D0%F3%5E%2AC%9A%A2v%99%BA%D8%13%A4%AB%B1j |
It’s large enough and contains some data (probably your orders). If we delete it and buy smth, it will appear again, but smaller one. Let’s try different orders:
0000000: dec8 aabc 7e39 277a 81fd c2f5 2920 86ad ....~9'z....) .. 0000010: f76c 1978 f51b 404e f802 f8ee cde9 0819 .l.x..@N........ 0000020: cf26 8484 1ec6 1fc6 |
0000000: dec8 aabc 7e39 277a 81fd c2f5 2920 86ad ....~9'z....) .. 0000010: f76c 1978 f51b 404e 61d3 0d32 9c78 4e48 .l.x..@Na..2.xNH 0000020: cf26 8484 1ec6 1fc6 |
You can notice only 8 byte changed. DES with ECB mode comes on mind. But we don’t know anything about the plaintext format, so how can we generate a valid ciphertext for the product id 2?
The most straight forward way is bruteforce. We can bruteforce key and then encrypt anything we want.
Also if you play a bit with PHPSESSID cookie, you can notice it’s encrypted in the “data” cookie too:
set PHPSESSID="A"*48 and login: ... 0000130: f381 4151 ddbf 5708 11b8 0290 a79b daa5 ..AQ..W......... 0000140: 5dbb 36b4 79d2 771f 2d91 6672 a90c ed48 ].6.y.w.-.fr...H 0000150: 2d91 6672 a90c ed48 2d91 6672 a90c ed48 -.fr...H-.fr...H 0000160: 2d91 6672 a90c ed48 2d91 6672 a90c ed48 -.fr...H-.fr...H 0000170: 5495 2c3f 766b 297b 18d2 1e15 f4f8 0740 T.,?vk){.......@ 0000180: e43b bd10 64ea 7026 9260 686f b828 d4e1 .;..d.p&.`ho.(.. ... |
See “AAAAAAAA” encrypted to “\x2d\x91\x66\x72\xa9\x0c\xed\x48”. Now let’s bruteforce:
Let’s try different charsets separately: 0-9, A-Z, a-z
. Also, DES key’s low bits are meaningless, so the real charsets are 02468
, ABDFHJLNPRTVXZ
and abdfhjlnprtvxz
.
#include <stdio.h> #include "brute.h" #include <openssl/des.h> void checker(char pw[8], int len) { DES_key_schedule k; unsigned char out[8] = ""; DES_set_key_unchecked((const_DES_cblock *)pw, &k); DES_ecb_encrypt((const_DES_cblock *)"AAAAAAAA", (DES_cblock *)out, &k, 1); if (!memcmp(out, "\x2d\x91\x66\x72\xa9\x0c\xed\x48", 8)){ printf("pwned: %s\n", pw); } } int main(int argc, char *argv) { bruteforce_set_debug(1); bruteforce_set_key("", 8); bruteforce_set_charset("02468", 5); bruteforce(6, (check_func_t)checker); bruteforce_set_charset("ABDFHJLNPRTVXZ", 14); bruteforce(6, (check_func_t)checker); bruteforce_set_charset("abdfhjlnprtvxz", 14); bruteforce(6, (check_func_t)checker); } |
$ gcc shop.c brute.c -lcrypto -O9 && time ./a.out Starting bruteforce with 6 threads... Charset: '02468' (5 bytes) Key: up to 8 bytes ... Starting bruteforce with 6 threads... Charset: 'ABDFHJLNPRTVXZ' (14 bytes) Key: up to 8 bytes ... Starting bruteforce with 6 threads... Charset: 'abdfhjlnprtvxz' (14 bytes) Key: up to 8 bytes ... pwned: rnntbddr Bruteforce ended real 5m42.424s user 27m34.039s sys 0m0.420s |
Soo we have password: rnntbddr. Looks like the actual password is rootbeer. Let’s decrypt the cookie:
import requests from urllib import * from Crypto.Cipher import DES r = requests.post("https://ctf.fluxfingers.net:2077/login.php", data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"}) del r.cookies['data'] # make small cookie r = requests.get("https://ctf.fluxfingers.net:2077/shop.php?buy=1", cookies=r.cookies) print DES.new("rnntbddr").decrypt(unquote_plus(r.cookies['data'])) |
$ py wu.py a:1:{s:6:"basket";a:1:{i:1;i:1;}} |
Oh, seems it’s a php-serialized data. We need product id 2, so:
r = requests.post("https://ctf.fluxfingers.net:2077/login.php", data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"}) r.cookies['data'] = quote_plus(DES.new("rnntbddr"). encrypt('a:1:{s:6:"basket";a:1:{i:2;i:1;}}\x00\x00\x00\x00\x00\x00\x00') ) r = requests.get("https://ctf.fluxfingers.net:2077/order.php", cookies=r.cookies) print r.content |
The flag for this challenge is spr4y4me.
PS: dictionary bruteforce would be faster
Injection way
Supposed by awesie from PPP.
This much cooler way is injecting an encrypted block. The idea is to craft a ciphertext for message:
...sket";a:2:{i:
| 00000002 | ;i:1;}}
We can encrypt “00000002” block using PHPSESSID cookie:
set PHPSESSID to xxAAAAAAA00000002AAAAAAAA (padding guessed experimentally) 0000140: 95d2 a231 d6a7 6cd4 2d91 6672 a90c ed48 ...1..l.-.fr...H 0000150: 9a1f 8712 a6d3 c9fa 2d91 6672 a90c ed48 ........-.fr...H 0000160: 6876 d91d 7655 0a35 8d0b 3643 4c8d 0ecf hv..vU.5..6CL... 0000170: 4c38 ff8a 9c93 d994 200a L8...... . |
Recall that “AAAAAAAA” is encrypted to “\x2d\x91\x66\x72\xa9\x0c\xed\x48”. Here you see “\x9a\x1f\x87\x12\xa6\xd3\xc9\xfa” between 2 encrypted “AAAAAAAA” blocks – it is encryption of “00000002”.
Now let’s get encryption of “;i:1;}}\x00”: we can adjust length of PHPSESSID cookie to make the last block be “;i:1;}}”.
We’ll encrypt 8 different lengths to see when new block appears:
for x in xrange(1, 9): r = requests.post("https://ctf.fluxfingers.net:2077/login.php", data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"}, cookies={"PHPSESSID": "A"*x}) r = requests.get("https://ctf.fluxfingers.net:2077/shop.php?buy=1", cookies=r.cookies) c = unquote_plus(r.cookies['data'])[0x158:] print x, c.encode("hex") |
Results:
1 d0f35e2a439aa27699bad813a4abb16a 2 405a89b7b4343ea02b50cabace0ad830 3 a27dc769d5a03b23d39919dcecc1cdcf 4 86a2a414eadac8cfdc278b150ebbec34 5 f76c1978f51b404ef802f8eecde90819cf2684841ec61fc6 6 e15762b5966480b0e0f4f96766468c86be48762cccbc8e70 7 5bbde0895be74e0a3c74c423c0347612f2b13147ee448e44 8 1f5c44958e5b8d692bd1c3d47ef078585eca063c66adf92d |
4 is to full block, we need minus one byte for “;i:1;}}\x00” – so the encryption is “d39919dcecc1cdcf”.
Now, the last step. We need blocks to divide like this:
...;a:2:{i: | 1;i:1;}}
The last block is full so we use “A”*4 as PHPSESSID and then we’are replacing the last block with
"\x9a\x1f\x87\x12\xa6\xd3\xc9\xfa" + "\xd3\x99\x19\xdc\xec\xc1\xcd\xcf"
:
r = requests.post("https://ctf.fluxfingers.net:2077/login.php", data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"}, cookies={"PHPSESSID": "A"*4}) r = requests.get("https://ctf.fluxfingers.net:2077/shop.php?buy=1", cookies=r.cookies) c = unquote_plus(r.cookies['data']) c = c[:-8] + "\x9a\x1f\x87\x12\xa6\xd3\xc9\xfa" + \ "\xd3\x99\x19\xdc\xec\xc1\xcd\xcf" r.cookies['data'] = quote_plus(c) r = requests.get("https://ctf.fluxfingers.net:2077/order.php", cookies=r.cookies) print r.content |
Result: The flag for this challenge is spr4y4me.
PS: It’s very hard to solve it in this way if you don’t know the format of the plaintext data.
Awesie (and all who solved this way) rocks :)
7 comments
Skip to comment form
Hi,
Can you help explain how can you get from the cookie “data”:
To this stream:
I have changed the order, but the cookie data always start with : “60C8 9D9B…”.
Thanks.
Author
After your login the cookie is large and starts with “60C8 9D9B …”
But if you *delete* this cookie and buy something, it will contain only data for your buy and will start with “dec8 aabc. …”
Oh, i see, thanks.
Hi,
First off, thanks for sharing, really helpful. Could you please explain why ‘A’ and ‘a’ are part of the charset range? I got confused since those are odd numbers discarded by the low bit right?
Author
0x40 @
0x41 A
0x42 B
0x43 C
…
B ^ 1 = C, so B is equal to C in des key.
But we can’t drop out A, because @ is not in a charset (charset must contain either X or X | 1)
Tha’s why we have 26 letters in alphabet, but we need to try 14 instead of 13.
Digits are better – they start with 0x30 so can reduce 10 digits to 5: 02468
Got it. Thanks for the explanation. Now I am ready for the next CTF, LoL!
i need some help with this really cool thing im trying to figure out(if you like money you may be interested and i dont mind shareing the info with you especially if we can figure it out together!) and no very little (close to nothing) about solving it because the answer seems to be in figuring out encoding and encryption or parsing or somthing, please get back to me thanks so much