主机安全——洋葱Webshell检测实践与思考
的形式,
sources:污染源,表示所有用户可控的输入点。
sinks:危险汇聚点,代表所有可能产生威胁的函数或操作。
sanitizers:无害处理,代表进行安全操作或过滤,使得样本不在具有安全风险。
洋葱检测引擎的语义、动态分析过程有所不同,语义污点追踪是从sinks触发,判断sinks中的变量或函数是否存在外界可控,而动态污点分析是从sources触发,判断这些污染源是否下沉至sinks点。
语义污点追踪
语义污点追踪先是对样本进行预处理,保证样本文件可以正常的解析为AST抽象语法树,之后寻找所有的sink点,从这些sinks出发,经过静态追踪判断sink点是否与外接输入相关。它是一种静态检测方法,不会真正的执行代码,只能分析代码表象,故为虚。流程图如下:
1. AST
AST(抽象语法树)是PHP 7 引入的一个新特性,在PHP收到一个请求或执行命令时,会首先进行词法和语法分析,生成AST,再生成字节码opcodes,继而继续执行并返回结果。正是由于AST的出现,我们对复杂多变的PHP Webshell的检测有质的提升。
形如最经典的shell一句话:
生成的抽象语法树如下(生成过程可参考https://astexplorer.net/):
2. 静态污点分析
静态污点分析过程可以分为三个部分,危险汇聚点、污染传播和污染源。
(1)危险汇聚点
在对样本解析生成语法树之后,第一步便是要寻找万恶之源-sinks,检测引擎汇集了几乎所有可能存在的风险点,一共可以分为代码执行、文件操作、动态操作、命令执行、外部组件、回调等。我们首先基于一个认知,所有的webshell,无论如何变形,都摆脱不了对上述风险点的利用。
(2) 污点传播
在收集了所有威胁参数节点之后,便针对这些节点进行逐一回溯追踪判断,进而判断数据流是否可控。洋葱检测引擎支持变量赋值、引用传值、函数调用、函数赋值、间接传值等多种方式。
(3) 污染源
污染源是第二步威胁节点追踪的的终点,如果在威胁节点追踪的过程中发现是外界可控参数或与外界可控变量相关,则直接判断为恶意webshell。常见的污染源有预定义变量、可控函数、变量覆盖函数等。
(4) 检测示例
function test($a){
eval($a);
}
$a = $_GET[“c”];
test($a);
上述代码是一种函数调用型webshell,语义追踪的过程为:首先发现eval危险函数,之后追踪eval参数$a,发现是在函数test中,然后再跟踪test函数的调用信息,最后锁定调用参数是否外部可控,其数据流模型图为:
间接影响的shell样本如下:
foreach ($_GET as $b){
if ($b == “c”){
$e = ‘e’;
}
if ($b == “m”){
$e .= ‘val($_GET[1]);’;
}
// …
}
eval($e);
通过外界输入间接控制变量进而构造出webshell文件。静态分析的过程为:追踪$e变量,发现在赋值节点中会被$b影响,并且$b变量受到外界控制,则判断为webshell文件。在这种情况下,洋葱检测引擎会提取所有与变量相关联的节点进行回溯,确保关联节点也不会收到外界影响,对应的数据流模型图为:
动态模拟执行
动态模拟执行部分是基于参数赋值+污点分析的方式实现的。模拟执行可以通过在运行时对外界参数进行模拟赋值的方式,使得代码可在不需要外界参数的情况下继续运行下去。而污点分析技术,可以将模拟值(Source点)标记为污点数据,通过追踪污点数据相关的信息是否流向敏感函数(Sink点),以此来发现可通过外界参数控制敏感函数参数的风险行为。
模拟执行检测过程可以分成4个阶段:
(1) 参数赋值
参数赋值是模拟执行的核心,首先寻找全局外部变量,如GET、POST、COOKIE等。然后通过符号计算推导出符合程序条件运行的虚拟值,并在运行时进行赋值,使得程序可在不需要外部参数的情况下运行起来。
(2) 污点标记
污点标记是整个模拟执行检测的前提,目前在不同的应用程序中进行污点标记的方法各不相同,php这里是借鉴了taint的实现方式,利用php变量的特殊标记和预留标志位,将标志位打点实现标记。污点标记是与参数赋值相结合的,赋值的参数在运行阶段便被打上标记。
(3) 污点传播
污点传播是模拟执行检测的保障,对于已被打上标记的参数变量,变量带有的污点会在程序流中传递,但在传递过程中可能存在污点丢失的情况,所以需要对字符串处理函数、加密函数和转换函数等进行处理,检测函数参数是否存在污点,若存在则将返回值打上标记,以保证污点标记向下传播。
(4) 污点检测
污点检测的核心点是敏感函数划分,如何判定为敏感函数是模拟执行检测的重点,首先要关注的是命令执行函数和代码执行函数,其次是可间接调用函数的动态调用、反射调用和回调类型,最后是风险程度较小的文件操作、数据库操作等。确定好敏感函数后,通过检测敏感函数的参数是否带有污点来确定是否为恶意文件。核心的检测逻辑便是敏感函数+可控参数。
$a = $_GET[‘code’];
$b = base64_decode($a);
eval($b);
?>
上述代码是一个简单的Webshell,参数经过base64解密后进行代码执行。动态模拟执行的过程为:首先寻找到外部变量,对外部变量$_GET[‘code’] 进行参数赋值且给$_GET[‘code’] 打上标记,然后赋值给$a。第二步是将$a用base64解密后赋值给$b,由于base64_decode hook后会检测参数是否存在污点,有则将返回值打上标记,故$b带有污点标记。最后经过Sink点(eval)执行且参数$b带有污点, 故判定为恶意文件。其数据流模型图为:
组合分析
静态的符号执行只能分析出代码所能呈现的逻辑结构,并不知道具体变量的内容。PHP复杂多变的动态特性是影响静态分析的关键性因素。比如在动态调用检测中,单纯的语义只能分析出存在动态调用函数,并不清楚具体是哪个函数调用,但直接告警很明显是不合理的,因此必须要动态的模拟执行配合才能确切的判断是否为恶意文件,其次在检测代码执行时,语义解析由于不清楚变量中的内容导致无法解析具体执行的代码内容,这一部分也是需要动态配合进行检测。
其次虽然动态检测在对抗静态混淆绕过方面具备相对优秀的检测能力,但在实现动态检测的过程中,还是遇到许多困难点,相对于传统正则引擎,这种方式也多了一些比较另类的绕过可能,总结这些绕过其核心的思路有以下两点:
1. 打断污点传播,使得经过Sink点的参数不具备污点,绕过检测。
2. 改变程序执行流,使得程序流在不传入特定值时不经过Sink点,从而绕过检测。
静态语义检测和动态污点检测都有其难以弥补的短板问题,发现问题才能解决问题,有对抗才能有提升。洋葱检测引擎建设了许多动静结合的策略,在保证低误报的前提下,尽可能的发掘代码中的威胁行为。静态语义分析主要为了保证动态污点能够准确的流向危险源,而动态模拟执行能提供函数调用链用于静态语义分析,这便是两者思想的结合点。
动静结合可以有效的解决代码中的分支问题,抽象语法树解析可以正确的识别分支逻辑,并把可能存在风险的分支内容扔给动态去处理。
关于报错终止问题,静态可先将代码封装为try-catch的方式,将报错代码忽略,而后交给动态,保证程序的继续进行。
关于变量覆盖问题,则可通过hook影响变量定义或赋值的函数来覆盖。
同时引擎也会提取动静态的检测结果进行整合分析,比如代码如下:
function test(){
/*
* eval code
* */
}
$a = ‘t’.’e’.’s’.’t’;
$a();
静态发现一个危险函数test,但是没有找到调用信息,而动态走到了这个危险函数中,通过两个检测结果的对比与结合,便可判断其为一个Webshell文件。当然就结合方面,引擎还有一段很长的路要走。
对抗与思考
检测过程解剖完成之后,对抗的思路也就变得很清晰了。检测引擎可能面临敏感源、污点传播、污染源三方面的对抗。
- 敏感源
检测引擎的敏感源必须考虑完备,如果出现一些函数,形如mb_eregi_replace、set_error_handler 等,可以执行代码并且不在引擎的监控列表内,那么就可以直接绕过引擎的检测,这是引擎可能面临的威胁之一。
例:mb_eregi_replace
mb_eregi_replace(‘.*’,$_GET[1],”,’e’);
当mb_eregi_replace的options为e时,可以执行进行代码执行。
- 污染传播
通过打断污染传播来构造webshell是一个非常常用且有效的绕过手段,也是检测引擎监控和防御的难点。打断污点的方式很多,比如利用报错、分支、间接影响、环境等等方式。
例:环境变量
putenv($_GET[“c”]);
eval(getenv(‘path’));
?>
首先通过putenv传递变量,之后获取变量中的path内容,那么只需要传入c=path=phpinfo();即可完成利用。
例:变量覆盖
define($_GET[a], $_GET[b]);
eval(A);
?>
先外部传入a=A 定义A变量,再通过传入b=phpinfo();给A变量赋值,即可完成利用,因此这就要求安全人员尽可能尽可能覆盖所有的变量传播方式,同时这也是一个积累与完善的过程。
- 污染源
通过寻找冷门的污染源进而绕过检测,也是检测引擎的风险点之一。比如filter_input也可以接受外部参数,如下例中通过传入参数c即可执行代码。
$a = filter_input(INPUT_GET,’c’);
eval($a);
?>
总结
在静态语法树分析方面,其实也可以实现局部的模拟执行功能,还原基本的变量,减轻动态检测的负担。在动态检测方面,可以结合优质的求解器等符号执行方法。
玉不琢,不成器。检测引擎的提升是一个不断沉淀与积累的过程,只有不断的积累攻击手法,举一反三,才能不断的提升检测能力和检测质量。通过这次比赛,我们也学习到了白帽子各种新鲜的技术,这也极大的提升了引擎的检测能力,同时也认识到检测引擎的不足,我们会对引擎更加细致的升级与改造。青山不改,绿水长流,我们后会有期!
感谢海星、海章对本文以及检测引擎建设的帮助与指导,感谢洋葱团队、研发团队、TSRC、腾讯蓝军、运营团队对检测引擎和本次活动的支持。
我们站在巨人的肩膀上,感谢炽天使、牛哥、职业欠钱、flyh4t、cradmin、刘水生、岳志飞、黄湘琦博士、达达、kb、snaker、huili、momo、zhixiang等前辈对洋葱webshell检测引擎的贡献。
最后再次感谢本次参与测试的白帽子们(排名不分先后):phithon,zsp,Omegogogo,w,goto:REinj,do9gy,蜂花护发素,白帽酱,鲸落,紫迹,星光,wuyx,tharavel,test123,monkeyd,impakho,fzxcp3,Lenka
参考
1. laruence. taint: https://github.com/laruence/taint
2. 王蕾, 李丰, 李炼,等. 污点分析技术的原理和实践应用[J]. 软件学报, 2017, 028(004):860-882.
3. PHP. https://www.php.net/
4. Webshell通用免杀思考:https://www.freebuf.com/company-information/235706.html
5. 青藤云安全|腾讯lake2 :Webshell检测的前世今生. https://zhuanlan.zhihu.com/p/136905818