HackTheBox – Baby SQL

In this write-up we will be visiting the baby sql challenge from HackTheBox.

I heard that *real_escape_string() functions protect you from malicious user input inside SQL statements, I hope you can’t prove me wrong…

We are presented with just a URL on the HackTheBox docker subdomain. We start by opening a browser and analyzing the functionality of the website. Upon navigating to the website in a browser, we are presented with what seems to be the source-code for the website as shown below.

<?php require 'config.php';

class db extends Connection {
    public function query($sql) {
        $args = func_get_args();
        return parent::query(vsprintf($sql, $args));

$db = new db();

if (isset($_POST['pass'])) {
    $pass = addslashes($_POST['pass']);
    $db->query("SELECT * FROM users WHERE password=('$pass') AND username=('%s')", 'admin');
} else {

The application expects an HTTP POST request from the client with a parameter named pass in the request body. The pass parameter is then sanitized via the addslashes function, effectively escaping any inserted quotation marks or other unwanted symbols. Once the input has been sanitized, it is used as a format parameter in the query string which is passed to the query function. The query function then uses the vsprintf function to format the final query string and perform the database query. This effectively blocks our attempts at performing traditional SQL injection.

Let’s assume the following input parameter.

' OR 1=1#

After having passed through the addslashes function, our input parameter would have been transformed as follows.

\' OR 1=1#

As a result, our input parameter will no longer terminate the quotation mark for the pass parameter insertion point and allow us to perform SQL injection. However, since the transformed parameter is passed to the vsprintf function, we can abuse the combination of addslashes and vsprintf to escape the quotation mark for the pass insertion point.

If we look at the implementation of the vsprintf function in PHP, which can be found here, we notice that it has a greedy format specifier consumption mechanism. Specifically, if the function receives an unrecognized format specifier, it will simply consume the specifier and remove it from the format string without inserting anything in its place. We can abuse this behaviour to consume escaping backslashes inserted by the addslashes function.

Let’s assume the following input parameter.

%1$' OR 1=1#

After having passed through the addslashes function, our input parameter would have been transformed as follows.

%1$\' OR 1=1#

The magic happens when this string is passed through the vsprintf function, which will consume the unrecognized %1$\ format specifier and remove it from the string, effectively leaving the terminating single quotation mark unescaped.

The format of the performed query is as shown below.

SELECT * FROM users WHERE password=('$pass') AND username=('%s')

In order to successfully perform SQL injection using the above mentioned method, we need to terminate the quotation mark and the parenthesis, and then insert an OR clause to perform our own arbitrary query. Finally, we must insert a comment to invalidate the part of the original query string that follows our input. A correct injection template is thus given as shown below, where ‘QUERY’ denotes the insertion point for our own arbitrary query.


We can script this in Python as follows.

import requests
import sys

url = ''

if len(sys.argv) > 1:
    body = { 'pass': "%1$')||{}#".format(sys.argv[1]) }
    print requests.post(url, data=body).text

Now that we have a script for performing arbitrary query injection, we can attempt to extract data using error-based SQL injection. We start by fetching the database version to know more about the remote database service.

As shown in the illustration above, the remote database is running MariaDB version 10.5.5. We can thus adapt our next queries to the MariaDB syntax and pull a list of tables from the database as shown below.

As shown in the illustration above, the database contains two tables named users and totally_not_a_flag respectively. We can now fetch entries from the totally_not_a_flag database as shown below.

And there you have it – the challenge has been solved!

This Post Has 4 Comments

  1. Hannan Haseeb

    Thank you so much for providing this write up. You don’t know how much it helps me. Thanks again!

    1. st4ckh0und

      Happy to be of help 🙂

  2. cnm

    So what i dont understand is if the vsprintf function consumes unrecognized format specifiers shouldn’t it work just by passing a ‘ through because the leading back slash should be consumed anyway? I noticed that this payload is very specific it has to be %1 $’ or %1$\’ . Why these characters specifically in that order? I tried a couple of different iterations and it has to be in that order or you get a bad charset error. Shouldn’t it matter because in the end all we want is the backslash to be consumed? Just trying to understand what led you add those specific characters and their number in that order??

Leave a Reply to cnm Cancel reply