hello

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
/*
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));

file协议读next.txt,但过滤了file和next。payload:http://45.147.231.180:8000/?x=fi�le:///ne�xt.txt 利用了url编码的不可见字符,以绕过str_contains,然后escapeshellargs又会去掉不可见字符。HTML URL 编码参考手册 (w3school.com.cn)大概%79以下都是可见字符。

然后访问第二层(虽然也不是一打开就看到源码,但是很容易就能看懂提示,然后拿到源码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
}
});

这里想了几种方法想要绕过(当然都失败了)

  1. 利用传入数组绕过indexOf,但file[]不会被读取为参数。

  2. 试图各种编码绕过,当然很快就放弃了(无头苍蝇属于是)

  3. fs.readFileSync绕过,但只有传入URL对象时才会调用url解码,而这里传入时已经是字符串了。
    readFileSync浅分析 | Sk1y’s Blog (sk1y233.github.io)

corCtf2022一道有意思的node题-腾讯云开发者社区-腾讯云 (tencent.com)

奇安信攻防社区-fs.readFileSync的利用 (butian.net)

当然,以上文章大同小异

最后的payload是http://45.147.231.180:8001/39c8e9953fe8ea40ff1c59876e0e2f28/read/?file=/next.txt/.,分析一下为什么能绕过检测并读取到next.txt

  1. /.的作用:(此处为Node环境)跟踪调试一下,可以追到path的normalizeString方法,注释为// Resolves . and .. elements in a path with directory names/next.txt%00/.path.basename(fpath)后得到.,而在fs.readFileSync(fpath)中一路走到function normalizeString(path, allowAboveRoot, separator, isPathSeparator),单个dot会被该方法忽略掉
    js_normalizeString_single_dot

Windows中该方法的调试信息

js_normalizeString_debug

因此去掉了/.而处理了正确的路径。(然而在Node环境下本来/next.txt/.应该就可以了,可惜这题是Bun起的环境,用到俺也不知道为啥会有的特性)

  1. %00的作用似乎是本题使用Bun环境的特性,Node没有该问题,如果本题为Node环境,则应该是/next.txt/.。应该是Bun下readFileSync的00截断,因此才能去掉后面/.的影响?

    Bun_test