3 This was my first ever real CTF (beside WUTCTF 19 from the Introduction to Security course). I started with some very simple challenge (50 points) called coffee break which was a custom cipher with an AES layer on top. I was able to solve it pretty fast which was quite motivating.
4 Overall, the challenges were quite funny and I learned a lot.
6 ## crypto: coffee break (solved by me)
7 The flag was given in encrypted form along with the encryption script written in python. The flag was encrypted twice. The first encryption was done with some custom math algorithm and the result was then encrypted with AES (ECB mode).
9 I first looked at AES since the ECB mode was configured and I knew from WUTCTF that this is quite easy to decrypt since it's a block cipher where each block is independent from all other blocks. After some minutes looking at the code I figured out that the AES key was given in plaintext (key2 + padding). So the outer layer of the encryption was solved.
11 Then I looked at the math encryption function which contained some reversible functions (addition, subtraction). So I started implementing a reversing function. The only problem here was that there was a modulo operator. But since I knew that the output can just contain characters I checked if a character code is out of the character space and then added the character code of A. I was quite surprised that this worked.
12 The decrypt function I developed was this:
14 def decrypt(key, text):
16 for i in range(len(text)):
17 char_code = ord(text[i]) - 0x20
18 dec = char_code - (ord(key[i % len(key)]) - 0x20) + 0x20
20 dec += (0x7e - 0x20 + 1)
26 This challenge took me about 45 minutes.
28 ## web: Option-Cmd-U (solved)
29 A web application written in PHP was given. The source code was available by appending a query param (`?action=source`). This and some other information was given in the source code of the web page. Also the following information was given: `the flag is in /flag.php, which permits access only from internal network`. The purpose of the web application was to download a given web page (entered by the user) and show a highlighted version of the page source (replacement for the browser's "view page source"-function).
31 In the source code there was an unused if-block with the following comment:
33 if (gethostbyname(idn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)) === gethostbyname("nginx")) {
34 // local access to nginx from php-fpm should be blocked.
35 echo 'Oops, are you a robot or an attacker?';
39 So my plan was to somehow inject nginx as hostname to get access to the flag.php file. I then tried different weird strings but they all didn't work. The solution (from another teammate) then was to enter a special unicode character which was ignored by the `file_get_contents` function.
41 flag: `SECCON{what_a_easy_bypass_314208thg0n423g}`
43 Since this was quite tricky to find a way, it took me about 5 to 6 hours.
45 ## web: fileserver (solved)
46 Since I like web challenges a lot, I couldn't wait to start the next one. A web application written in ruby using a library called WEBrick. The challenge creator developed a web service using the WEBrick http library. The application provided directory listings for all URLs which ended with a slash. To list all files from a directory, the function Dir.glob was used. Since this function allowed some wildcards, there was some filtering implemented which was the painpoint of the application (As we noticed later during the challenge).
47 For directory listings, actually the following call was issued: `files = Dir.glob(".#{req.path}*")`. The prepending dot made sure that the path is relative. So we could start our request path with a dot so we get something like `../` to traverse back. Since dot's were filtered (resulting in a bad request) this was obsolete.
49 Since a lot of those weird URLs are getting changed by the browsers automatically (or the url got interpreted as a search request), we had to use `curl` with the command `--path-as-is` which sends the path to the server as given by the user without applying any transformation.
51 If the request path didn't end with a slash, it was trying to find the given path as a file and serve the file if it exists.
52 The following matching was used: `matches = Dir.glob(req.path[1..])`. Since paths which don't end with a slash could use a dot, we could traverse back to the root here, but the flag name was obfuscated with a random string so it was hard to figure out the exact path.
54 A teammate then found out that we could enter a null byte (URI escaped) before the path. Using this trick, we were able to get a directory listing of `/tmp/flags` which contained the obfuscated filename of the the flag file. We then thought, that we solved the challenge because the final url would just be: `fileserver.chal.seccon.jp:9292/%00/tmp/flags/#{SecureRandom.alphanumeric(32)}.txt`
55 But we were wrong, because the file name can't contain any null byte.
57 So we looked again at the source code to find some other bugs. We then looked very detailed at the filtering function for the glob wildcards.
59 The source code for this function was the following:
64 %w(* ? [ { \\).each do |char|
74 # check if brackets are paired
76 path[path.index(bad_char)..].include? ?}
78 path[path.index(bad_char)..].include? ?]
86 The code checks for occurence of some special characters in the following order: `* ? [ { \\`. If the character is `{` it checks for an ending character and returns the result of the check. So it would be possible to add a `{[<some-content>]` and `is_bad_path` would return false. It turns out that this is already the solution to access the flag file. Since `{p,q}` is a special `Dir.glob` expression which matches either the `p` or `q` we could use this to get the flag file. We can get rid of the `[` by using it as `q`, which results in the following request path: `{/tmp/flags/<filename>.txt,[}`.
88 The final URL was then: `fileserver.chal.seccon.jp:9292/%7B/tmp/flags/<filename-of-flagfile>.txt,%5B%7D`
90 flag: `SECCON{You_are_the_Globbin'_Slayer}`
92 This challenge was also a little bit tricky. I would compare it to Option-Cmd-U. I also spent about 5 to 6 hours on this challenge.