Some Tricks of Bypass php waf

  1. 1. 引入
  2. 2. 绕过 waf
  3. 3. PHP 转义序列
  4. 4. php 可变函数
  5. 5. 改进输入检测
  6. 6. get_defined_functions
  7. 7. 字符数组
  8. 8. 参考

起因是年前看了一篇How To Exploit PHP Remotely To Bypass Filters & WAF Rules,现在搜了一下发现已经有翻译了。感觉升华也没什么好扩展的,也不太好拿去投稿了,思考了一下,感觉还是当作学习笔记来写算了。

以下实现环境均在 php 7.0.31 版本上,并且把 waf 因素考虑在内。

引入

首先来看一段 php 代码:

1
2
3
4
5
6
7
<?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 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。

可变函数不能用于例如 echoprintunset()isset()empty()includerequire 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。

看一下官方示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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 文件内容修改为

1
2
3
4
5
6
7
<?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函数名

1
2
3
4
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中加入空格就好了。

也可以使用一些内置变量,不再赘述,如下图所示

参考

How To Exploit PHP Remotely To Bypass Filters & WAF Rules


Article Author: Zeddy

Article Link: https://blog.zeddyu.info/2019/02/28/Some-Tricks-of-Bypass-php-waf/index.html

Copyright Notice: With the exception of the special statement at the beginning of the article, all articles can be reprinted in accordance with the CC BY 4.0 agreement with the author's permission.

Sqli-lab速刷记录(1-53) Hexo Template render error 解决方案

Comments