首页 » PHP教程 » phpa技巧_一类PHP RASP实现

phpa技巧_一类PHP RASP实现

访客 2024-11-24 0

扫一扫用手机浏览

文章目录 [+]

RASP观点

RASP(Runtime Application self-protection)是一种在运行时检测攻击并且进行自我保护的一种技能。
早在2012年,Gartner就开始关注RASP,惠普、WhiteHat Security等多家国外安全公司陆续推出RASP产品,时至今日,惠普企业的软件部门出售给了Micro Focus,RASP产品Application Defender随之易主。
而在海内,去年知道创宇KCon大会兵器谱展示了JavaRASP,前一段韶光,百度开源了OpenRASP,去年年底,360的0kee团队开始测试Skywolf,虽然没有看到源码和文档,但它的设计思路或许跟RASP类似。
而商业化的RASP产品有OneAPM的OneRASP和青藤云的自适应安全产品。
在海内,这两家做商业化RASP产品做得比较早。

phpa技巧_一类PHP RASP实现

那么RASP到底是什么呢?它到底是若何事情的呢?

phpa技巧_一类PHP RASP实现
(图片来自网络侵删)

我的WAF天下不雅观

为了表述方便,暂且把RASP归为WAF的一类。
从WAF所在的拓扑构造,可以大略将WAF分为如下三类,如下图所示:

以阿里云为代表的云WAF以中间人的形式,在HTTP要求到达目标做事器之提高行检讨拦截。

以ModSecurity为代表的传统WAF在HTTP要求到达HTTP做事器后,被Web后端容器阐明/实行之前检讨拦截HTTP要求。

RASP事情在Web后端阐明器/编译器中,在漏洞代码实行前阻断实行流。

从上图中WAF所处的位置可以看出,云WAF和传统WAF的检讨拦截HTTP要求的紧张依据是HTTP Request,实在,如果站在一个非安全从业者的角度来看,这种检测办法是奇怪的。
我们可以把Web做事看做是一个接管输入-处理-输出结果的程序,那么它的输入是HTTP要求,它的输出是HTTP相应。
靠检测一个程序的输入和输出来判断这个程序的运行过程是否有害,这不奇怪吗?然而它又是可行且有效的,大多数的Web攻击都能从HTTP要求中找到蛛丝马迹。
这种检测思路是云WAF和传统WAF能有效事情的缘故原由,也是它们的缺陷。

笔者一贯认为,问题发生的地方是监控问题、办理问题的最好位置。
Web攻击发生在Web后端代码实行时,最好的防护方法便是在Web后端代码实行之前推测可能发生的问题,然后阻断代码的实行。
这里的推测并没有这么难,就彷佛云WAF在检讨包含攻击payload的HTTP要求时推测它会危害Web做事一样。
这便是RASP的设计思路。

好了,上面谈了一下笔者个人的一些意见,下面开始谈一谈PHP RASP的实现。

RASP在后端代码运行时做安全监测,但又不侵入后端代码,就得切入Web后端阐明器。
以Java为例,Java支持以JavaAgent的办法,在class文件加载时修正字节码,在关键位置插入安全检讨代码,实现RASP功能。
同样,PHP也支持对PHP内核做类似的操作,PHP支持PHP扩展,实现这方面的需求。
你可能对JavaAgent和PHP扩展比较陌生,实际上,在开拓过程中,JavaAgent和PHP扩展与你打仗的次数比你意识到的多得多。

PHP扩展简介

有必要先容一下PHP阐明的大略事情流程,根据PHP阐明器所处的环境不同,PHP有不同的事情模式,例如常驻CGI,命令行、Web Server模块、通用网关接口等多个模式。
在不同的模式下,PHP阐明器以不同的办法运行,包括单线程、多线程、多进程等。

为了知足不同的事情模式,PHP开拓者设计了Server API即SAPI来抹平这些差异,方便PHP内部与外部进行通信。

虽然PHP运行模式各不相同,但是,PHP的任何扩展模块,都会依次实行模块初始化(MINIT)、要求初始化(RINIT)、要求结束(RSHUTDOWN)、模块结束(MSHUTDOWN)四个过程。
如下图所示:

在PHP实例启动时,PHP阐明器会依次加载每个PHP扩展模块,调用每个扩展模块的MINIT函数,初始化该模块。
当HTTP要求来临时,PHP阐明器会调用每个扩展模块的RINIT函数,要求处理完毕时,PHP会启动回收程序,倒序调用各个模块的RSHUTDOWN方法,一个HTTP要求处理就此完成。
由于PHP阐明器运行的办法不同,RINIT-RSHUTDOWN这个过程重复的次数也不同。
当PHP阐明器运行结束时,PHP调用每个MSHUTDOWN函数,结束生命周期。

PHP核心由两部分组成,一部分是PHP core,紧张卖力要求管理,文件和网络操作,另一部分是Zend引擎,Zend引擎卖力编译和实行,以及内存资源的分配。
Zend引擎将PHP源代码进行词法剖析和语法剖析之后,天生抽象语法树,然后编译成Zend字节码,即Zend opcode。
即PHP源码->AST->opcode。
opcode便是Zend虚拟机中的指令。
利用VLD扩展可以看到Zend opcode,这个扩展读者该当比较熟习了。
下面代码的opcode如图所示

<?php$a=1;$b=2;print $a+$b;>

Zend引擎的所有opcode在http://php.net/manual/en/internals2.opcodes.list.php 中可以查到,在PHP的内部实现中,每一个opcode都由一个函数详细实现,opcode数据构造如下

struct _zend_op { opcode_handler_t handler;//实行opcode时调用的处理函数 znode result; znode op1; znode op2; ulong extended_value; uint lineno; zend_uchar opcode;};

如构造体所示,详细实现函数的指针保存在类型为opcode_handler_t的handler中。

设计思路

PHP RASP的设计思路很直接,安全圈有一句名言叫统统输入都是有害的,我们就跟踪这些有害变量,看它们是否对系统造成了危害。
我们跟踪了HTTP要求中的所有参数、HTTP Header等统统client端可控的变量,随着这些变量被利用、被复制,信息随之流动,我们也跟踪了这些信息的流动。
我们还选取了一些敏感函数,这些函数都是引发漏洞的函数,例如require函数能引发文件包含漏洞,mysqli->query方法能引发SQL注入漏洞。
大略来说,这些函数都是大家在代码审计时关注的函数。
我们利用某些方法为这些函数添加安全检讨代码。
当跟踪的信息流流入敏感函数时,触发安全检讨代码,如果通过安全检讨,开始实行敏感函数,如果没通过安全检讨,阻断实行,通过SAPI向HTTP Server发送403 Forbidden信息。
当然,这统统都在PHP代码运行过程中完成。

这里紧张有两个技能问题,一个是如何跟踪信息流,另一个是如何安全检讨到底是若何实现的。

我们利用了两个技能思路来办理两个问题,第一个是动态污点跟踪,另一个是基于词法剖析的漏洞检测。

动态污点跟踪

对PHP内核有一些理解的人该当都知道鸟哥,鸟哥有一个项目taint,做的便是动态污点跟踪。
动态污点跟踪技能在白盒的调试和剖析中运用比较广泛。
它的紧张思路便是先认定一些数据源是可能有害的,被污染的,在这里,我们认为所有的HTTP输入都是被污染的,所有的HTTP输入都是污染源。
随着这些被污染变量的复制、拼接等一系列操作,其他变量也会被污染,污染会扩大,这便是污染的传播。
这些经由污染的变量作为参数传入敏感函数往后,可能导致安全问题,这些敏感函数便是沉降点。

做动态污点跟踪紧张是定好污染源、污染传播策略和沉降点。
在PHP RASP中,污染源和沉降点显而易见,而污染传播策略的制订影响对RASP的准确性有很大的影响。
传播策略过于严格会导致漏报,传播策略过于宽松会增加系统开销。
PHP RASP的污染传播策略是变量的复制、赋值和大部分的字符串处理等操作传播污染。

动态污点跟踪的一个小小好处是如果一些敏感函数的参数没有被污染,那么我们就无需对它进行安全检讨。
当然,这只是它的副产物,它的大浸染在漏洞检测方面。

动态污点跟踪的实现比较繁芜,有兴趣的可以去看看鸟哥的taint,鸟哥的taint也因此PHP扩展的办法做动态污点跟踪。
PHP RASP中,这部分是基于鸟哥的taint修正、线程安全优化、适配不同PHP版本实现的。
在发行过程中,我们也将遵守taint的License。

在PHP阐明器中,全局变量都保存在一个HashTable类型的符号表symbol_table中,包括预定义变量$GLOBALS、$_GET、$_POST等。
我们利用变量构造体中的flag中未被利用的一位来标识这个变量是否被污染。
在RINIT过程中,我们通过这个方法首先将$_GET,$_POST,$_SERVER等数组中的值标记为污染,这样,我们就完成了污染源的标记。

污染的传播过程实在便是hook对应的函数,在PHP中,可以从两个层面hook函数,一是通过修正zend_internal_function的handler来hook PHP中的内部函数,handler指向的函数用C或者C++编写,可以直接实行。
zend_internal_function的构造体如下:

//zend_complie.htypedef struct _zend_internal_function { / Common elements / zend_uchar type; zend_uchar arg_flags[3]; / bitset of arg_info.pass_by_reference / uint32_t fn_flags; zend_string function_name; zend_class_entry scope; zend_function prototype; uint32_t num_args; uint32_t required_num_args; zend_internal_arg_info arg_info; / END of common elements / void (handler)(INTERNAL_FUNCTION_PARAMETERS); //函数指针,展开:void (handler)(zend_execute_data execute_data, zval return_value) struct _zend_module_entry module; void reserved[ZEND_MAX_RESERVED_RESOURCES];} zend_internal_function;

我们可以通过修正zend_internal_function构造体中handler的指向,待完成我们须要的操作后再调用原来的处理函数即可完成hook。
另一种是hook opcode,须要利用zend供应的API zend_set_user_opcode_handler来修正opcode的handler来实现。

我们在MINIT函数中用这两种方法来hook传播污染的函数,如下图所示

当传播污染的函数被调用时,如果这个函数的参数是被污染的,那么把它的返回值也标记成污染。
以hook内部函数str_replace函数为例,hook后的rasp_str_replace如下所示

PHP_FUNCTION(rasp_str_replace){ zval str, from, len, repl; int tainted = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, \公众zzz|z\公众, &str, &repl, &from, &len) == FAILURE) { return; }//取参 if (IS_STRING == Z_TYPE_P(repl) && PHP_RASP_POSSIBLE(repl)) { tainted = 1; } else if (IS_STRING == Z_TYPE_P(from) && PHP_RASP_POSSIBLE(from)) { tainted = 1; }//判断 RASP_O_FUNC(str_replace)(INTERNAL_FUNCTION_PARAM_PASSTHRU);//调用原函数实行 if (tainted && IS_STRING == Z_TYPE_P(return_value) && Z_STRLEN_P(return_value)) { TAINT_MARK(Z_STR_P(return_value)); }//污染标记}

首先获取参数,判断参数from和repl是否被污染,如果被污染,将返回值标记为污染,这样就完成污染传播过程。

当被污染的变量作为参数被传入关键函数时,触发关键函数的安全检讨代码,这里的实现实在跟上面的类似。
PHP的中函数调用都是由三个Zend opcode:ZEND_DO_FCALL,ZEND_DO_ICALL 和 ZEND_DO_FCALL_BY_NAME中某一个opcode来进行的。
每个函数的调用都会运行这三个 opcode 中的一个。
通过挟制三个 opcode来hook函数调用,就能获取调用的函数和参数。
这里我们只须要hook opcode,便是上面第二幅图示意的部分,为了让读者更加清晰,我把它复制下来。

如图,在MINIT方法中,我们利用Zend API zend_set_user_opcode_handler来hook这三个opcode,监控敏感函数。
在PHP内核中,当一个函数通过上述opcode调用时,Zend引擎会在函数表中查找函数,然后返回一个zend_function类型的指针,zend_function的构造如下所示

union _zend_function { zend_uchar type; / MUST be the first element of this struct! / struct { zend_uchar type; / never used / zend_uchar arg_flags[3]; / bitset of arg_info.pass_by_reference / uint32_t fn_flags; zend_string function_name; zend_class_entry scope; union _zend_function prototype; uint32_t num_args; uint32_t required_num_args; zend_arg_info arg_info; } common; zend_op_array op_array; zend_internal_function internal_function;};

个中,common.function_name指向这个函数的函数名,common.scope指向这个方法所在的类,如果一个函数不属于某个类,例如PHP中的fopen函数,那么这个scope的值是null。
这样,我们就获取了当前函数的函数名和类名。

以上的行文逻辑因此RASP的角度来看的,先hook opcode和内部函数,来实现动态污点跟踪,然后通过hook函数调用时运行的三个opcode来对监控函数调用。
实际上,在PHP内核中,一个函数的调用过程跟以上的行文逻辑是相反的。

当一个函数被调用时,如上文所述,根据这个函数调用的办法不同,例如直接调用或者通过函数名调用,由Zend opcode,ZEND_DO_FCALL,ZEND_DO_ICALL 和 ZEND_DO_FCALL_BY_NAME中的某一个opcode来进行。
Zend引擎会在函数表中搜索该函数,返回一个zend_function指针,然后判断zend_function构造体中的type,如果它是内部函数,则通过zend_internal_function.handler来实行这个函数,如果handler已被上述hook方法更换,则调用被修正的handler;如果它不是内部函数,那么这个函数便是用户定义的函数,就调用zend_execute来实行这个函数包含的zend_op_array。

现在我们从RASP的角度和PHP内核中函数实行的角度来看了动态污点跟踪和函数的hook,接下来,我们须要对不同类型的关键函数进行安全检测。

基于词法剖析的攻击检测

传统WAF和云WAF在针对HTTP Request检测时有哪些方法呢?常见的有正则匹配、规则打分、机器学习等,那么,处于PHP阐明器内部的PHP RASP如何检测攻击呢?

首先,我们可以看PHP RASP可以获取哪些数据作为攻击检测的依据。
与其他WAF一样,PHP RASP可以获取HTTP要求的Request。
不同的是,它还能获取当前实行函数的函数名和参数,以及哪些参数是被污染的。
当然,像传统WAF一样,利用正则表达式来作为规则来匹配被污染的函数参数也是PHP RASP检测的一种方法。
不过,对付大多数的漏洞,我们采取的是利用词法剖析来检测漏洞。
准确的来说,对付大多数代码注入漏洞,我们利用词法剖析来检测漏洞。

代码注入漏洞,是指攻击者可以通过HTTP要求将payload注入某种代码中,导致payload被当做代码实行的漏洞。
例如SQL注入漏洞,攻击者将SQL注入payload插入SQL语句中,并且被SQL引擎解析成SQL代码,影响原SQL语句的逻辑,形成注入。
同样,文件包含漏洞、命令实行漏洞、代码实行漏洞的事理也类似,也可以看做代码注入漏洞。

对付代码注入漏洞,攻击者如果须要成功利用,必须通过注入代码来实现,这些代码一旦被注入,一定修正了代码的语法树的构造。
而追根到底,语法树改变的缘故原由是词法剖析结果的改变,因此,只须要对代码部分做词法剖析,判断HTTP要求中的输入是否在词法剖析的结果中霸占了多个token,就可以判断是否形成了代码注入。

在PHP RASP中,我们通过编写有限状态机来完成词法剖析。
有限状态机分为确定有限状态机DFA和非确定有限状态机NFA,大多数的词法剖析器,例如lex天生的词法剖析器,都利用DFA,,由于它大略、快速、易实现。
同样,在PHP RASP中,我们也利用DFA来做词法剖析。

词法剖析的核心是有限状态机,而有限状态机的构建过程比较繁琐,在此不赘述,与编译器中的词法剖析不同的是,PHP RASP中词法剖析的规则并不一定与这门措辞的词法定义同等,由于词法剖析器的输出并不须要作为语法剖析器的输入来布局语法树,乃至有的时候不必区分该措辞的保留字与变量名。

在经由词法剖析之后,我们可以得到一串token,每个token都反响了对应的代码片段的性子,以SQL语句

select username from users where id='1'or'1'='1'

为例,它对应的token串如下

select <reserve word>username <identifier>from <reserve word>users <identifier>where <reserve word>id <identifier>= <sign>'1' <string>or <reserve word>'1' <string>= <sign>'1' <string>

而如果这个SQL语句是被污染的(只有SQL语句被污染才会进入安全监测这一步),而且HTTP要求中某个参数的值是1'or'1'='1,比拟上述token串可以创造,HTTP要求中参数横跨了多个token,这很可能是SQL注入攻击。
那么,PHP RASP会将这条HTTP要求剖断成攻击,直接阻挡实行SQL语句的函数连续运行。
如果上述两个条件任一不成立,则通过安全检讨,实行SQL语句的函数连续运行。
这样就完成了一次HTTP要求的安全检讨。
其他代码注入类似,当然,不同的代码注入利用的DFA是不一样的,命令注入的DFA是基于shell语法构建的,文件包含的DFA是基于文件路径的词法构建的。

在开拓过程中有几个问题须要把稳,一个是\0的问题,在C措辞中,\0代表一个字符串的结束,因此,在做词法剖析或者其他字符串操作过程中,须要重新封装字符串,重写一些字符串的处理函数,否则攻击者可能通过\0截断字符串,绕过RASP的安全检讨。

另一个问题是有限状态自动机的DoS问题。
在一些非确定有限状态机中,如果这个自动机不接管某个输入,那么须要否定所有的可能性,而这个过程的繁芜度可能是2^n。
比较常见的例子是正则表达式DoS。
在这里不做深入展开,有兴趣的朋友可以多理解一下。

谈论

在做完这个RASP之后,我们转头来看看,一些问题值得我们思考和谈论。

RASP有哪些优点呢?作为纵深防御中的一层,它加深了纵深防御的维度,在Web要求发生时,从HTTP Server、Web阐明器/编译器到数据库,乃至是操作系统,每一层都有自己的职责,每一层也都是防护攻击的阵地,每一层也都有对应的安全产品,每一层的防护侧重点也都不同。

RASP还有一些比较明显的优点,一是对规则依赖很低,如果利用词法剖析做安全检测的话基本不须要掩护规则。
二是减少了HTTP Server这层攻击面,绕过比较困难,绝大多数基于HTTP Server特性的绕过对RASP无效。
例如HPP、HPF、畸形HTTP要求、各种编码、拆分关键字降落评分等。
三是误报率比较低。
从比较空想的角度来说,如果我的后端代码写得非常安全,WAF看到一个包含攻击payload的要求就拦截,这也属于误报吧。

RASP的缺陷也很明显,一是支配问题,须要在每个做事器上支配。
二是无法像云WAF这样,可以通过机器学习进化考验规则。
三是对做事器性能有影响,但是影响不大。
根据我们对PHP RASP做的性能测试结果来看,一样平常来说,处理一个HTTP要求所花费的性能中,PHP RASP花费的占3%旁边。

实在,跳出RASP,动态污点跟踪和hook这套技能方案在能做的事情很多,比如性能监控、自动化Fuzz、入侵检测系统、Webshell识别等等。
如果各位有什么想法,欢迎和我们互换。

参考文献

鸟哥taint https://github.com/laruence/taint

Thinking In PHP Internals

http://php.net

PHP Complier Internals

自动机理论、措辞和打算导论

标签:

相关文章

JSP日记本,记录成长,见证时光

随着互联网的飞速发展,我们的生活变得越来越丰富多彩。在这个信息爆炸的时代,我们似乎每天都有无数的事情需要记录。而日记,作为一种传统...

PHP教程 2024-12-23 阅读0 评论0

JSP文件流,构建高效动态网页的利器

随着互联网技术的飞速发展,动态网页已经成为了网络时代的主流。JSP(Java Server Pages)作为一种流行的服务器端技术...

PHP教程 2024-12-23 阅读0 评论0

介绍测运势代码,科技与神秘学的完美结合

随着科技的飞速发展,我们的生活越来越离不开计算机。而在计算机技术中,测运势代码成为了神秘学与科技结合的产物。通过测运势代码,我们可...

PHP教程 2024-12-23 阅读0 评论0

介绍淘宝代码,介绍电商平台背后的秘密

随着互联网的飞速发展,电子商务已经成为人们生活中不可或缺的一部分。作为国内最大的电商平台,淘宝吸引了无数消费者的目光。你是否曾想过...

PHP教程 2024-12-23 阅读0 评论0

介绍汽车出厂代码,解码汽车身份的密码

汽车出厂代码,作为汽车身份的“身份证”,在汽车行业发挥着举足轻重的作用。它如同人类指纹,独一无二,承载着汽车的生产、销售、维修等重...

PHP教程 2024-12-23 阅读0 评论0