CTF 2006 Prequal Walkthrough: Web 3.0
The Web 3.0 category was almost entirely an exercise in SQL injection. There were a few gotcha's, and the last one was pretty interesting. While the "lessons" for each stage are important, the solutions to each stage seemed slightly less than real-world in their feel.
100: Read the source
The first impulse for any web vulnerability analysis should be to start by reading the source of the page. In this case, the "admin.html" file was commented out in the source for the FORM tag. By just replacing the filename in the URL, you immediately got the key.
200: Classic SQL injection
For testing for SQL injection vulnerabilities, you usually just put in a single quote, and hit "Submit". This immediately made the service blow up with a 500-series "internal server error". The next step to SQL injection is to attempt to "OR" your way into a "TRUE" SQL select statement. To ditch the remainder of the SQL statement, the easiest method is to comment out the rest with the "--" comment syntax. This results in:
' OR 1=1 --
Using this for the username, the key is displayed.
Generally, these sorts of programming mistakes come from a style of "user authentication" that only tests if the SQL statement returns a row, but doesn't check the results. For example:
    $result = sql_query("SELECT id FROM users WHERE username='$username' AND password='$password'")
    if (rows($result)>0) {
        // let them in
    }
The "' OR 1=1 --" attack comments out the password match, and kicks back an empty single-row SQL result (NULL id). This, unfortunately, is a very common mistake in SQL-based attempts at authentication.
300: Filtered SQL injection
As in 200, it's immediately obvious that it is vulnerable to SQL injection attacks because it blows up with a username of "'". However, this time, we get fancy error tracing. How handy! Clicking on the "..." on the right side, we can examine the SQL statement, and various code snippets.
At first glance, it seems the solution for 200 would work here as well. After trying it, though, we're greeted with an error from the script itself, saying "Invalid characters in username". This means filtering is happening before passing the username variable to the SQL statement. Since we know it's not the "'" character, we check on the "-" character. As it turns out, this is also an invalid character. So, it seems we must construct a query without commenting out the rest of the SQL.
We try to make up a stand-alone "OR" test in what would otherwise be a valid query. A quick attempt:
' OR 1=1 OR username='
We're greeted again with the "Invalid characters" message, and we now consider that spaces are not allowed. No worries! Either use the other style of SQL commenting to separate your statement elements, or just don't use spaces at all. Both of these solutions do the trick:
'OR''=''OR''='
'/**/OR/**/1=1/**/OR/**/username='
For this challenge, since we were being given the actual source to the script, it would have been more "real-world appropriate" to have to defeat a more robust check of the SQL result. To verify that a valid row was returned (rather than an all-NULL row) a test of getting back a valid username from the database might have been fun. This would have required a slightly more advanced SQL injection:
'/**/OR/**/username/**/like/**/'%'/**/OR/**/''='
This would have returned the first user in the table.
400: Structural SQL injection and session tracking
Since one should never forget to read the source, we notice a hidden FORM variable named "sessKey". Curiously close to 31337, we play with it a little, but without any immediate results. We do notice, however, that it is being incremented with each new HTTP request. Saving this for later, we move on.
The "'" username test shows another vulnerable script, but this time without the helpful error tracing. Trying "' OR 1=1 --" blows it up, though. Suspecting that the query may require a more valid result than an all-NULL row, assuming none of the database fields changed, we try what should be a valid query:
' AND username='
This does NOT blow up the server, but (of course) doesn't let us in.
Now assuming that the results of the SQL query are being examined more closely this time, we move on to the next SQL injection trick: "UNION SELECT". With this syntax, the goal is to produce NO rows in the SQL preceeding the injection, and then manufacture a row within the injection. Assuming the same query as 300 ("SELECT username, password") we try:
' UNION SELECT 1,2 --
And discover a new error, which says that session is already in use. Sounds like we made it past the SQL test! Moving back to the hidden variable, we set it to 31337 and try again. The key greets us.
As with 300, a more "real-world" implementation maybe would have checked the password. In that case, (again assuming the same style of password authentication from 300: md5) we could have built an md5 string, and passed the matching clear-text in the "password" variable, with the injection:
' UNION SELECT 'pwnd','758d31f351ce11bf27c522f516cb4c20' --
500: IP scavenger hunt
This URL immediately greeted us with a cookie named "source_ip". Ignoring this for a moment, we find the script is SQL injectable, and with:
' OR 1=1 --
We get a new error message that says we can only log in from 127.0.0.1. Assuming that this test is being done on the cookie rather than, say, looking at the socket address or some web server environment variable, we examine the cookie more closely.
Connecting from the same IP address, we get the same cookie value. Connecting from a different IP address, we get a slightly different cookie. So, we conclude that the cookie value holds an encoded IP address. The strings are the same length as the IP address strings, so we assume this is a character scrambling of some kind. Seeing that repeated characters in the IP address do not repeat in the scrambled version, we further conclude this is like a position-based scrambling. (i.e. In the string "127.0.0.1", the "0" in position 5 could be scrambled to "A", but "0" in position 7 uses a different scrambling, and could be "H".) Seeing that the same characters in the same position are always the same, we're pretty sure it's not some kind of "real" encryption that would depend on prior characters in the string, etc.
Now, if you're like team 1@stPlace, you've already done a bunch of the other lower-level challenges in all the other categories at this point in the game. We decided we needed to sample an IP address that matched "127.0.0.1" as closely as possible. ;) We shelled into kenshoto.allyourboxarebelongto.us with one of our Pwnage exploits, bound to localhost, and talked to the server, care of netcat:
(echo "GET / HTTP/1.0"; echo ""; sleep 5) | nc -s 127.0.0.1 localhost 8500
This gave us the needed cookie string, and we were on our way. :) However, it seems this was not the intended approach, and this route was closed after Kenshoto realized what had happened. D'oh.
The "correct" solution is to sample as many IP addresses as you can. A pattern emerges that shows each character in the string has been rotated, sometimes with some weird skips, reversals, etc. For example in the first character position, "1" is "\010", "2" is "\013", "6" is "\017". After the prequals, we checked our IP samples, and found that we had enough character matches to produce almost the entire cookie string. We were only missing the second zero and the terminal "1":
\010\012K\231\256R0\0371
We had samples for the 9th character of "3" and "2" being "\037" and "\036", so we assumed "1" would be "\035". For the 7th character "0", we only had a single sample of "1" being "\332", so we guessed the direction and found "0" at "\333". Passing this to "curl", the key was found:
curl -b 'source_ip="\010\012K\231\256R\333\037\035"' -F username="' OR 1=1 --" -F password= http://kenshoto.allyourboxarebelongto.us:8500/fa926fa448eba493b863d035d1a19597
This challenge was fun both times we solved it. The first time was ninja-fun, and the second time was fun because it was effectively a scavenger hunt for controlled IP address sources. Very cool problem.
UPDATE: Thanks to Sk3wlMaster for pointing out that the scrambling used was actually just XOR, not a rotation cipher. The IP string was just XOR'd against the string "\x39\x38\x7C\xB7\x9e\x7C\xEB\x31\x2C\x16\x7E\x92\x59\x36".
UPDATED UPDATE: Invisigoth has clarified that the cipher string used for the XORing is actually an RC4 stream, seeded with "kenshoto".

ctf 2006 prequals