介绍
命令执行是非常危险的一种漏洞,会造成严重的信息泄露,还可能导致 getshell,本文介绍如何去绕过一些简单的防命令执行,仅仅提供一种思路,实际情况各有不同。
先来看一段存在命令执行漏洞的 php 代码:
1 |
|
第二行试图校验 system, exec, passthru 三个函数,但这明显是远远不够的,还有很多其他的函数可以执行命令,但这里让我们假设只有这三个,这个脚本运行于 CloudFlare WAF 之后,
尝试读取/etc/passwd
我们尝试请求/cfwaf.php?code=system(“cat /etc/passwd”); 这时 WAF 拒绝了我们的请求,我们可以假设是因为 /etc/passwd 这一重要文件引起,所以我们尝试修改为”cat /etc\$u/passwd”,这时候由于存在 system 关键词,会输出’invalid syntax’,接下来考虑怎么样可以避免直接使用 system。
php 字符串编码
\[0-7]{1-3}代表八进制
\x[0-9A-Fa-f]{1,2}代表 16 进制
\u{[0-9A-Fa-f]{1,2}}代表 Unicode 编码,通常实现为 UTF-8
并不是所有人都知道这么多表示字符串的语法,再加上 php 变量函数,这将会成为我们绕过 WAF 或者 filter 的瑞士军刀。
php 变量函数
php 支持通过变量调用函数,比如
1 | $var(args); |
都可以当做函数调用。这也就意味着我们可以进行如下转化:
1 | system("ls") |
第三种直接将 system 转为 16 进制来执行,这样我们做到了不直接使用 system。
这种技术并不是适用于所有函数,比如 echo, print, unset(), isset(), empty(), include, require 这种类函数语句是不能用上面这种方式代替的。
不使用引号
代码修改为
1 |
|
可以看到我们加入了单引号和双引号的判断,这就意味着我们上面那些语句都会触发过滤。
幸运的是,在 PHP 里,我们表示一个字符串并不一定要用引号,比如$a = (string)foo;
接着我们尝试如下转换:
1 | echo "foo"; |
执行过后你会发现连 string 都没必要使用,直接用括号就可以转换。也就是说我们执行(system)(ls);
是被允许的,但是我们不能用 system,所以尝试字符串拼接(sy.(st).em)(ls);
。或者我们加两个参数,绕过对于 code 参数的检测?a=system&b=ls&code=$_GET[a]($_GET[b])
也就是说我们可以通过字符串拼接和变量调用两种方式来绕过。
get_defined_functions
get_defined_functions 返回一个数组,假设是\$arr, 里面包含所有的内置和自定义函数。
1 | $arr = get_defined_functions(); |
比如我们尝试执行
1 | php -r 'print_r(get_defined_functions()[internal]);' | grep 'system' |
即可知道 system 函数是第几个函数,我这里是第 503,所以绕过上面的过滤时就可以这样:
1 | # 先找到system的位置 |
字符数组
在 php 中可以用数组的形式取到字符串的每一个字符,这样我们就可以先定义一个包含所有需要的字符的字符串,然后通过下标取到字符再拼接的方式构造出我们需要的字符串。
1 | # 相当于执行(system)(ls /tmp) |
这样在请求的时候我们可以取FILE路径名的字符来构造我们的 payload。