PHP立体安全:一网打尽攻击向量
所谓攻击向量,就是指黑传递有效负载或恶意结果而可以访问计算机或网络服务器的路径或方法。
PHP的安全并不只有危险函数,这只是冰山一角。本文将介绍PHP从汇编层面到框架层面直到标准层面的所有攻击向量。
攻击向量枚举图
PHP代码层面
介绍
危险函数是PHP代码层面的主要审计对象,它们通常有如下功能:
1.进行I/O操作
2.进行命令/代码执行
3.进行网络交互
4.进行数据库交互
实际上这些功能都是开发所不可缺少的,功能设计本身没有问题,只是被滥用了。
实例-preg_replace的e参数
这是thinkphp2.x的一个历史漏洞,存在于/ThinkPHP/Lib/Think/Util/Dispatcher.class.php
// $depr是取得的操作系统目录分隔符,可以认为等效如下形式
$depr='\/';
...
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
...
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
构造特定路由访问即可控制反向引用2处的值,又因为这里是双引号可以解析变量,利用PHP复杂变量进行命令执行.
例如/any_1/any_2/any_3/${phpinfo()}即可触发代码执行。(为了直观,省略了一些对路由机制的处理,实际上前几个any位置会被丢弃)
攻击向量分析
在PHP代码层面,本质上就是利用PHP已有的一些敏感功能,无论是phar反序列化,或者是file_put_content文件操作函数,还是call_user_function之类的回调函数,本身都是没有问题的,只是被恶意滥用,达到了开发者预期之外的效果。
PHP实现层面
介绍
PHP官方提供了PHP的C语言源码,可以在php/php-src: The PHP Interpreter (github.com)下载到源码,方便查看PHP函数的C语言实现。悠悠这里调试的PHP源码是分支PHP-5.6版本。
在PHP实现层面,C语言的业务逻辑缺陷外显成一个又一个的PHP的小trick,包括open_basedir的软链接绕过、is_file触发phar反序列化等等。
实例-strip_tags实现缺陷
$str = strip_tags($str, '<audio>');
//本意是设置白名单只允许标签audio存在
// 但是可以任意位置加斜杠,不影响audio白名单,这是一个底层trick
// 例如<a/udio href=’‘》会变成<a udio href=''>,再配合javascript伪协议触发xss
这一trick成因来自于底层的C语言实现,我们来看一下。
关键代码如下
while (i < len) {
switch (c) {
case '\0':
break;
case '<':
...
if (state == 0) {
...
} else if (state == 1) {
depth++;
}
break;
...
case '>':
if (depth) {
depth--;
break;
}
...
if (allow) {
if (tp - tbuf >= PHP_TAG_BUF_SIZE) {
pos = tp - tbuf;
tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1);
tp = tbuf + pos;
}
*(tp++) = '>';
*tp='\0';
if (php_tag_find(tbuf, tp-tbuf, allow)) {
memcpy(rp, tbuf, tp-tbuf);
rp += tp-tbuf;
}
tp = tbuf;
}
break;
发现此时只是在内存开辟空间,没有进行白名单处理。跳到php_tag_find函数。
while (!done) {
switch (c) {
case '<':
*(n++) = c;
break;
case '>':
done =1;
break;
default:
if (!isspace((int)c)) {
if (state == 0) {
state=1;
}
// 这个地方就是trick出现的原因
if (c != '/') {
*(n++) = c;
}
} else {
if (state == 1)
done=1;
}
break;
}
c = tolower(*(++t));
}
*(n++) = '>';
*n = '\0';
if (strstr(set, norm)) {
done=1;
} else {
done=0;
}
efree(norm);
return done;
我们可以看到,c等于”/“时不被计数,而它的位置并没有限定,导致trick诞生。
攻击向量分析
这种攻击向量中,PHP代码层面并不是危险函数,甚至是用于过滤的安全函数,但是底层实现逻辑与官方文档原意并不完全相同,导致非预期行为引发漏洞。实际上,PHP的大量trick都来源于此,即使开发者小心翼翼的按照官方文档进行开发,充分考虑安全因素,仍然可能生产不安全的代码。
PHP汇编层面和扩展层面
介绍
即使PHP的代码层面毫无问题,C语言实现层面完美无瑕,仍然可能存在攻击向量。那就是二进制安全,在汇编层面具有漏洞。
实例-htmlspecialchars()缓冲区溢出
bug编号: 60965
PHP版本: 5.4
bug描述: 40个字节以上长实体将导致缓冲区溢出。
<?php
echo htmlspecialchars('
"""""""""""""""""""""""""""""""""""""""""""""
',ENT_QUOTES, 'UTF-8', false), "\n";
在这个实例中,攻击向量变得非常隐秘,它不是PHP使用者的问题,也不是PHP开发者的问题,而是二进制层面的漏洞。
实例-不安全的.so扩展
这是一个自定义的恶意扩展,修改php.ini,增加extension=youyou.so启用之后,就可以调用system(),通过原先不存在的函数获得权限等,自定义的函数当然是不会被waf拦截的。
这里演示的是.so作为后门的用途,但实际上很多扩展本来不是作为后门,只是开发功能时留下了二进制层面的漏洞,被加以滥用了而已。
攻击向量分析
这一层面防不胜防,与代码层面的缺陷可以由个人用户自行修补不同,汇编和扩展层面寻常开发者不能自己修补,所以危害巨大,容易成为突破口。
C特性、操作系统层面
介绍
PHP语言毕竟底层是C语言,在毫不设防的情况下,C语言的特性就会保留且暴露出来。
实例-CVE-2015-2348
操作系统在识别字符串时,认为\0字符是一个字符串的结束符号。PHP源码的ext/standard/basic_functions.c中对长度的安全检查受限于C语言特性失效。php版本<=5.4.39 、5.5.0-5.5.23、5.6.0-5.6.7时存在。
move_uploaded_file($_FILES['file']['tmp_name'],"/tmp/youyou.php\x00.png")
攻击向量分析
C语言特性为安全过滤等操作带来了不便,好在PHP官方对此积极修复,实际上PHP8.0在以windows作为服务器环境时仍然存在大量类似的安全缺陷,例如>转为.问题。
PHP框架层面
介绍
现代框架一般由model,controller,view,router,middle部分组成,其中model包括与数据库的交互脚本,controller包括对业务逻辑的具体实现,view是前端的视图,router控制访问的请求的处理器行驶路由权限,middle作为中间件完成各种过滤功能。框架本身提供了更多更新的功能,可能会被开发者滥用。
实例-CodeIgniter框架sql缓存滥用
CI框架的model层已经封装了result()方法来获取数据库查询得到的数据,这其中经过大量复杂的解耦,我们直接看重点,省略部分过程,看到主要功能类。通过result找到它的具体逻辑result_object()
找一下这个类的属性来源。
继续看它的用途。
也就是说结果早就出来了储存在某个属性里....
我们惊奇的发现,这里优化性能时把数据库查询的缓存运用ORM技术经过反序列化存在了一个文件里。这是一个奇特的框架功能,但是由于写入控制的很好,本身没有安全问题。
写入逻辑不可控,只是功能比较奇特,但是不能利用。不过,如果遇到一个这样的控制器。
由于这里没有对..进行过滤,存在一个目录穿越漏洞,虽然在别处控制了目录权限限制/var/html/www目录执行,但是可以借助框架的那个特殊功能触发反序列化构造利用链条扩大攻击面。
攻击向量分析
框架的大量功能构建在PHP代码层面之上,它们本身可能是安全的,但是却可以作为新的”自定义“函数与业务逻辑捧出火花产生安全问题,是不容忽视的攻击向量来源。
PHP标准层面
介绍
web技术有许许多多的技术标准例如GraphQ、Jwt等等,它们的设计都是为了满足某种开发需求,但是在具体实现的时候可能会出现一些问题。
实例-OAtuh2.0实现缺陷
这是OAuth官网写的OAuth2.0技术标准脉络。
正如官方所说,OAuth2.0的作用是供用户授权访问第三方应用。涉及权限的问题,就是敏感的问题。
The OAuth 2.0 authorization framework enables a third-party
application to obtain limited access to an HTTP servic
这是微软对OAtuth授权代码授予的说明图。
这是微软对OAtuh隐式授权流的说明图。
我们可以发现,授权代码授予方式与隐式授权流的不同在于使用场景有无后端,前者是拥有后端,所以发送了the app's cilent_id,后者没有后端,没有提供这一信息。
如果采用了错误的实现方式,让前者在OAuth服务器上把acess token与cilent_id绑定,根据不同的client_id确定了可访问资源的范围,后者如果也是用的相同实现方式,相当于为none的client_id绑定了所有acess token,没有清晰的拥有者,同时对应了多个资源,就会出现问题。
采用错误实现的时候,我们可以通过正常操作获取acess token,然后等待它更新token,通过重放的方式修改图中的hidden sign改变获取资源的范围,绕过身份认证。
正确的实现方式应该是把acess token和资源绑定,有clent_id则连带绑定,没有则忽略。
攻击向量分析
PHP标准层面的漏洞是致命的,因为标准设计是为了解决某些开发上的问题,而具体实现时开发者可能不会考虑其安全性的问题,某种具有缺陷的实现思路一旦传播开来就会产生大面积的安全缺陷。尤其是标准设计本身具有难度,开发者难以察觉问题所在。RSA标准曾经使用过一种圆锥曲线密钥,后来发现存在数学上的缺陷,但是高明的开发者也不可能发现标准本身具有这种问题,可是不怀好意的人利用它却不是一件难事。
总结
PHP的安全是立体的、多维度的安全,从宏观到微观有各种各样的攻击面。本文枚举了PHP所有可能的攻击向量,虽然所举实例并不丰富,但是足够经典,如有不足还望指正。