污点传递理论在Webshell检测中的应用 -- PHP篇
原创:阿里云云安全中心 来自于公众号: 阿里安全响应中心
前言
Webshell是网站入侵的常用后门,利用Webshell可以在Web服务器上执行系统命令、窃取数据、植入病毒、勒索核心数据、SE0挂马等恶意操作,危害极大。 所谓Webshell,主要指可以被例如apache、tomcat、nginx在内的webserver即时解释执行的脚本语言编写而成的文本文件,其本质是一种text文本文件。因其隐秘性、基于脚本、灵活便捷、功能强大等特点,广受黑客们的喜爱,因此Webshell的检测也成为云安全防御的重点,甚至成为网站安全防御的一个标配。 在阿里云云安全中心,Webshell检测已是主机安全系统的标配功能。安骑士是阿里云安全自研的主机安全系统,Webshell检测是其基础功能之一,安骑士会实时监控Webshell文件的植入,经过后台的检测引擎分析后,实时向客户推送入侵告警信息。 近几年攻防对抗不断升级,防御的挑战越来越大,攻防的战场已经不再是谁见的样本多,而越来越转向方法论层面的对抗,攻击者往往会倾向于找到某种方法论体系,通过体系层面,来对防守方发起挑战。安骑士Webshell检测系统在对抗的过程中,逐步发展出了静态规则+动态规则+词法ast解析+动态模拟执行+机器学习等多种综合手段,目的也是为了尽量提高攻击绕过的门槛和成本,缓解Webshell攻击问题。 在之前的系列文章中,我们讨论了PHP和JSP Webshell的攻击方方法论,从这篇文章开始,我们会转换视角,着重从防守方视角,用体系化的方式介绍当前业内Webshell检测与防御的主要方法论以及具体技术细节。目的是希望能给业内同行带来一些启发,共同进步,提升检测防御能力。 其实从本质上说,所谓的恶意代码,从编程语言的视角来看,也不过是一种符合语法规范的、具备某种特定功能的可执行代码罢了。可能很多同学会有疑问,对于防守方来说,怎么会有什么方法论呢?防守方更多的日常工作不就是跟在黑客的后面,通过捕获更多的样本,添加更多的规则,不断提升检测水位吗? 今天这篇文章,笔者希望向大家阐明一个概念,防守方不但有方法论,而且作为防守方,日常的工作重点恰恰就是在方法论和研究和落地应用上,因为只有将问题上升到方法论这个层面上,攻防双方的信息不对等问题才能得到缓解和解决。 作为系列文章的第一篇,本文主要讨论关于污点传播理论方法论的研究与落地,更多的检测方法论以及其他恶意代码场景的讨论,我们会在今后的文章里逐步呈现。
污点传递理论概述
污点传递、或者说污点追踪,是一个泛概念,它在不同的应用领域都可以发挥良好的作用。 一般来说,污点分析可以抽象成一个三元组<sources,sinks,processor>的形式,其中 source 即污点源,代表直接引入不受信任的数据或者机密数据到系统中 sink 即污点汇聚点,代表直接产生安全敏感操作(违反数据完整性)或者泄露隐私数据到外界(违反数据保密性) processor 即数据流处理,代表整个数据传输和处理的过程(例如加密、编码处理),外部输入的数据经过processor处理后会得到一个适合软件核心模块处理的数据形式.
以PHP为例的污点传递过程 污点分析就是分析程序中由污点源引入的数据,在经过数据流处理后,传播到污点汇聚点后,是否符合预设的策略。这里的策略是一个泛概念,例如: 以XSS检测为例的污点分析过程 识别污点源和污点汇聚点是污点分析的前提。目前,在不同的应用程序中识别污点源和汇聚点的方法各不相同,这其实是一个泛概念,在不同的场景下,污点源和汇聚点污点分析会表现出不同的形式,这里的场景例如: 不同的系统模型 编程语言之间的差异 待跟踪分析的对象的差异
以 Web 应用程序漏洞检测为例,污点源及其关键属性如下: Document Form Input element Form History Select option Location and Link Window
对于污点汇聚点来说,可以从概念上大致分为3类: 使用启发式的策略进行标记。例如在webshell检测中,将来自程序外部输入的数据统称为“污点”数据,保守地认为这些数据有可能包含恶意的攻击数据 根据具体应用程序调用的 API 或者重要的数据类型,手工标记源和汇聚点。例如在webshell检测中,将file_get_contents这一类危险函数的执行结果标记为污点,继续后续的跟踪 使用统计或机器学习技术自动地识别和标记污点源及汇聚点.
污点传递理论应用场景
本文主要介绍的是安骑士Webshell检测中污点理论的应用,但实际上,污点传递理论在网络安全领域还可以有很多其他的应用,笔者这里列举一二:
污点传递在PHP Webshell检测中的落地方式
首先需要说明的是,方法论只是对一类或某几类特殊现象的一般性概括,既然是概括,那么就存在内涵与外延的问题,污点传递理论应用于PHP Webshell检测也同样必须遵从这个道理。准确地说,污点传递理论只适用于对”PHP一句话小马“这个特殊场景问题起作用,对于不符合”PHP一句话小马“特征的PHP Shell,污点传递理论也是无能为力的。 这里,对”PHP一句话小马的污点传递检测“进行一个形式化定义: 一切外部可控的输入都是有害的,通过跟踪有害变量,检查其是否被传入了某些敏感函数
做动态污点跟踪关键点是设定好污染源、污染传播策略和沉降点。而我们要标记和追踪的对象是源代码本身,类似于ring3的地位,要实现对ring3的追踪,就需要下沉监控点到ring0层,在PHP中就是zend解释器本身。 做动态污点跟踪关键点是设定好污染源、污染传播策略和沉降点。而实现这一目的的方式有很多,各家厂商的实现方式虽然在整体上类似,但是在实现细节上也各有不同,本文介绍一种比较常见的做法,抛砖引玉。
0x1:污染源标记
在PHP解释器中,全局变量都保存在一个HashTable类型的符号表symbol_table中,包括预定义变量GLOBALS、$_COOKIE、_GET、$_POST等。 我们利用变量结构体中的flag中未被使用的一位来标识这个变量是否被污染。在初始化(RINIT或MINIT)过程中,我们首先将????????????、_POST、$_SERVER...等数组中的值标记为污染,这样,我们就完成了污染源的标记。
0x2:沉降点
当被污染的变量作为参数被传入关键函数时,触发关键函数的安全检查代码,PHP的中函数调用不外乎三个Zend opcode某一个: ZEND_DO_FCALL ZEND_DO_ICALL ZEND_DO_FCALL_BY_NAME
每个函数的调用都会运行这三个 opcode 中的一个。通过劫持三个 opcode 来hook函数调用(opcode hook),就能获取调用的函数和参数。 如图,在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;
- };
复制代码
得到 zend_function 指针后,然后判断zend_function结构体中的type。 如果它是内部函数,则通过 zend_internal_function.handler 来执行这个函数,如果 handler 已被上述hook方法替换,则调用被修改的 handler 如果它不是内部函数,那么这个函数就是用户定义的函数,就调用 zend_execute 来执行这个函数包含的 zend_op_array。当执行到被修改的 handler 后,我们就可以在函数中编写自定义的污点记录或者污点检查策略。
0x3:污染传播策略
在PHP Webshell中,污染源和沉降点相对是确定的,而污染传播策略的制定影响对准确性有很大的影响。 传播策略过于严格会导致漏报(under-taint),传播策略过于宽松会增加系统开销以及导致过度传播(over-taint)。
PHP RASP的污染传播策略包括: 污染的传播过程其实就是hook php内部函数,使污点信息可以跟随执行过程顺畅流动,在PHP中,可以从两个层面来hook函数, 修改 zend_internal_function 的 handler 来hook PHP中的内部函数,handler指向的函数用C或者C++编写,可以直接执行。我们可以通过修改 zend_internal_function 结构体中 handler 的指向,待完成我们需要的操作后再调用原来的处理函数即可完成hook。 hook opcode function,需要使用zend提供的 API zend_set_user_opcode_handler 来修改opcode的 handler 来实现。
1、zend_internal_function handler hook
zend_internal_function的结构体如下:
- //zend_complie.h
- typedef 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;
复制代码
可以在MINIT函数中,hook待传播污染的函数
2、opcode handler 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_AEGIS_POSSIBLE(repl)) {
- tainted = 1;
- } else if (IS_STRING == Z_TYPE_P(from) && PHP_AEGIS_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是否被污染,如果被污染,将返回值标记为污染,这样就完成污染传播过程。
污点检测和规则持续运营的互补与融合
接下来的问题就是,有了污点传递理论,是否就需要安全运营再添加规则了呢?是否就可以高枕无忧了呢?答案是否定的。 实际上,笔者建议防守方同学,更应该讲污点追踪看做是一个AK47,武器本身是静止的,要发挥武器的作用,还需要不断为其添加更多的弹药。 我们前面说过,污点追踪引擎有3个主要的组件,即: 其中,污点传递涉及到底层引擎的工程化程度,具体来说就是上一章节所说的Zend引擎的完善度,这个完善度具体指哪些方面,我们后文会详说。 我们这小节,来重点讨论一下”污点源标记“与”汇聚点检查“这2个环节。实际上,污点源标记和汇聚点检查是需要安全运营基于攻防经验来人工设定的,它并不会自己从天上掉下来。 从安全攻防的角度来看,污点检测引擎实际上是一个规则驱动的引擎。以外部信息源为例,PHP中可以作为外部信息源的方式并不单只$GPC这一种,网络请求/系统指令/测信道/DNS等方式,理论上都可以作为外部信息源,向代码文件中传入外部任意可控数据流。
安骑士在面对这个问题时,采用了数据驱动的思路,通过将数据抽取和数据挖掘两个步骤分开,可以更高效地生成专家规则,因为对于攻击方来说,它的主要武器在于PHP手册以及Zend源码,通过深度翻阅手册文档,往往可以找到大量的冷门和生僻的PHP函数,通过这种方式来绕过防守方的检测。 这里列举了几个典型的例子, 这部分内容主要是经验对抗与数据挖掘方面的话题,笔者在将来的文章,可能会考虑单独开一篇文章来单独讲。
显式信息流追踪 -- 污点传递的优势
污点传播分析中的显式流分析就是分析污点标记如何随程序中变量之间的【数据依赖关系】传播。 以上图所示的程序为例,变量 a 和 b 被预定义的污点源函数 source 标记为污点源。 假设 a 和 b 被赋予的污点标记分别为taint_a 和 taint_b。 由于第 5 行的变量 x 直接数据依赖于变量 a,第 6 行的变量 y 直接数据依赖于变量 b,显式流分析会分别将污点标记 taint_a 和 taint_b 传播给第 5 行的变量 x 和第 6 行的变量 y。 又由于 x 和 y 分别可以到达第 7 行和第 8 行的污点汇聚点,在污点汇聚点,我们就可以按照预设的策略得出结论。 了解了显式信息流追踪这一点,对PHP Webshell检测有什么意义呢?它能提供一个新的视角,一个新的看待恶意PHP Webshell的视角。我们称之为”静态可重入单模态样本问题“。 传统的webshell样本,我们称之为“可重入单模态样本”,这类样本尽管可以利用php的大量tricky特性、使用各种编码、加密手段,代码形式可以极尽复杂,例如: 对这类样本,究其根本,其本质都是“可重入的、单模态的”,就是说不管运行多少次,谁来运行(受害者 or 旁路沙箱),其运行结果都是一样的。
从污点追踪理论的角度来说,它解决的是【显式污点传递问题】,即污点信息流的传递在样本代码中是显式的,只要模拟执行一遍即可100%模拟,引擎不再关心具体的代码形式,而转而关心具体的代码行为。 接下来的问题就是,这种方法论有没有问题呢?答案是肯定的,有,而且有很多。
隐式信息流追踪 -- 污点传递的挑战
这章的标题中,用了”挑战“这个词,意思是说,尽管存在很多困难,但总体来说,还是在理论可解决的范畴内的,还没有超过理论和工程上的极限。 我们在前面的章说过,污点追踪引擎有3个主要的组件,即”污点源标记“、”污点传递“、”汇聚点检查“。其中污点源标记和汇聚点检查都是安全运营同学通过规则设定的,对抗的本质还是在数据和人上。而”污点传递“这一点和具体Zend的实现有关。 我们把这一整类问题,都概括为”隐式信息流追踪问题“,所谓隐式信息流,是指分析污点标记如何随程序中变量之间的【控制依赖关系】传播,也就是分析污点标记如何从条件指令传播到其所控制的语句。 在上图所示的程序中,变量 X 是被污点标记的字符串类型变量,变量 Y 和变量 X 之间并没有直接或间接的数据依赖关系(显式流关系),但 X 上的污点标记可以经过控制依赖隐式地传播到 Y。
具体来说,由第 4 行的循环条件控制的外层循环顺序地取出 X 中的每一个字符,转化成整型后赋给变量 x,再由第 7 行的循环条件控制的内层循环以累加的方式将 x 的值赋给 y,最后由外层循环将 y 逐一传给 Y。 最终,第 12 行的 Y 值和 X 值相同,进入sink汇聚点检查。 但是,如果不进行隐式流污点传播分析,第 12 行 的变量 Y 将不会被赋予污点标记,分析程序将无法进入sink汇聚点进行有效检查。 除了if-else隐式传递,PHP中还有很多其他隐式数据流传递方式,例如: 通过引用,改变原始参数的传递过程 通过错误处理函数实现参数传递
对于污点追踪引擎来说,最大的挑战就是对Zend底层runtime的接管度,安骑士目前也在这方面进行持续的努力和提升,并且已经获得了一定的阶段性进展。 但是,遗憾的是,仅仅做到对Zend的完全接管,也无法100%实现污点追踪,这就是我们下一章要讨论的问题,污点传递追踪方法论的理论瓶颈。
差分攻击 -- 污点传递的理论瓶颈
我们在之前的文章中,详细讨论了PHP Webshell绕过的各种方法论,其中有一项叫做”差分攻击方法论“,这种攻击方式是专门针对动态污点沙箱的。 本质上说,差分攻击攻击的对象是”污点传递跟踪“这个过程,这里所谓的”差分“是从信息的角度来说的,在 网络安全领域盛行一句话,
差分攻击之所以能成立,主要是因为很多离线旁路沙箱并不是部署在”问题发生的地方“,从而导致了信息的丢失,这种信息丢失问题可以被攻击者利用,构成了所谓的差分攻击。 通过外部参数实现if控制流差分,使污点追踪沙箱进入错误的分支 通过外部参数实现函数名差分,使污点追踪沙箱调用了错误的函数
类似的例子还有很多,我们发现,一旦我们建立了差分这个攻击概念之后,污点追踪理论的边界就瞬间被突破,加上PHP语言本身的灵活性,PHP恶意样本检测的问题,一下子就被带到了一个更加困难的领域。
关于针对差分攻击进行特征工程以及机器学习方法应用的理论前景
针对上一章提到的不利局面,防守方就毫无办法了吗?答案是否定的。 通过一段时间的研究后,我们发现,差分问题也并不是毫无办法,通过有效利用信息,以及融合机器学习的方法,是有希望将攻防不对等的天平再次拉回到防守方这一边的,只是这一次,简单的规则就不再那么管用了,需要用上机器学习,而这里面的重要,并不是说需要特别前沿的算法,而在于特征工程。 由于篇幅限制,对于这部分的讨论,我们将放在下一篇系列文章中,我们在后面的讨论中,会详细讨论云盾安骑士是如何针对差分攻击进行创造性的特征工程,以及在此基础上融合机器学习,极大缓解在差分问题上,攻守双方的不对称地位的。
|