webshell检测方式深度剖析---语法语义检测(选读篇)

开篇

又到了周末,大好时间不能浪费,翻看之前下载的论文,发现有一篇讲述的webshell检测。通读一遍后发现作者的检测思路很新颖,但是看到后边也有些地方看的云里雾里,特写篇文章整理一下作者的思路和我的疑问,作为webshell检测这个系列的补充。

本来想着把这些疑问想通之后再把文章发布出来,但在这些问题的泥潭里挣扎一段时间后我发现,我应该是永远想不明白了,因为其中的核心逻辑作者写的实在是太模糊了。其实也可以理解,这篇论文毕竟是发表在信息安全杂志上的,作者为了知识专利考虑也会删减一些核心流程,只要文章大致逻辑能够自洽即可。

所以我在文章中也把这些疑问抛出来,也给出了一些自己的思路,大家可以一起思考。

下面我们进入正题。

基于语义分析的检测模型

使用正则匹配模型来构建webshell检测系统的最大优点就是简单方便,不需要太深奥的检测逻辑。但是缺点也非常明显,灵活性不够导致缺乏上下文的理解能力,基本只能检测“傻白甜”式的一句话webshell,或者逻辑非常简单的webshell。

下面我们用一个示例来展示正则匹配的痛点所在。

这是一个典型的preg_replace代码执行漏洞的样例webshell:

preg_replace(['/.*/e'], '\0', $_REQUEST[2333]);

preg_replace的第一个参数是待匹配的正则,但是在正则的尾部可以追加一些特殊字符来控制一些匹配行为,比如加入’i’代表不区分大小写,加入’e’代表对匹配后的结果使用eval函数来做代码执行。

下面是一个用来检测preg_replace函数代码执行漏洞的正则:

preg_replace\s*\(\s*['"]\s*/.+/e\s*['"]\s*,\s*[^\$]{150,},

思路是通过匹配第一个参数中是否包含’e’来判断是否该preg_replace调用会存在代码执行的风险。
但是,如果我把该webshell稍微做一下变形:

$data = "/.*/\ne\n\n     is\n";
preg_replace($data, '\0', $_REQUEST[2333]);

这个正则表达式就搞不定了,甚至这个简单的webshell你很难写出一个可用的正则来匹配它。

正则表达式在匹配webshell上实在太不灵活了,所以作者给出了一个基于语义检测的系统模型,主要用来拟补正则表达式匹配检测对上下文语义理解的不足。

下图为检测系统模型图
系统模型图

下面我们来详细分析每个模块的作用和流程。

预处理模块

预处理模块对接收的待检测模块进行词法分析和语法分析。

词法分析将文件中的有效代码转换为语言片段Tokens,PHP解释器当前使用的re2c(一个根据正则表达式生成c语言代码的程序)和规则定义文件Zend_language_scanner.l(php源码Zend目录下)来生成Tokens。

Tokens的具体格式在我们之前的文章中叙述过,这里不再赘述。Tokens随后会被送入词法分析系统进行下一步处理。

语法分析采用bison根据PHP定义的语法规则(php源码Zend目录下的Zend_language_parser.y文件)还原Tokens中的程序逻辑,构建出文件的完整抽象语法树AST。

文件预处理从语法角度区分了可执行文件和非可执行文件,包含语法错误的文件是无法构建出语法树的,所以这样也省去了无效文件的检测。同时,可以从程序语法角度来判断文件中是否有php代码(有些恶意代码故意被隐藏在日志文件中),而不用再去简单的通过文件后缀名来判断是否为php文件。

下面给出一个从源代码构建出语法树的示例。

源代码如下:

$a = 123;
$b = "hi~";
echo $a, $b;

生成的语法树如下:
抽象语法树

当然,将源代码构建成抽象语法树还有更简单的方法,github上有个名为PHP-Parser的项目,专门用来构建PHP的AST,且兼容PHP的个版本,非常方便。

污点子树提取模块

单文件构建的语法树并不庞大,但是分支繁多,因此本模块需要对语法树进行剪枝取枝操作,进一步定位产生恶意行为的污点子树。模块工作流程图如下:
污点子树提取模块

AST上每个节点都有所属的类型,PHP7解释器生成的AST共有97种(Zend/zend_ast.h中定义),用PHP-Parser生成的类型数量不尽相同,但都大致可分为如下四类:

  • stmt声明:表示函数定义,方法定义,类定义等;
  • expr表达式:表示变量定义,函数调用等;
  • scalar标量:用于表示字符串,整型,预定义常量等;
  • 其他类型:

根据常见的webshell所包含的恶意行为,如系统命令执行,eval恶意代码执行等,调用的关键AST节点进行风险评估,手工生成节点风险评估表,如下所示(部分):

名称类型备注风险等级
Eval_Expr代码执行函数高危
Shellexec_Expr系统命令执行函数高危
FuncCall_Expr动态变量函数调用高危
Assign_Expr赋值语句中危
If_Stmt条件判断安全
Echo_Stmt字符串输出安全

当检测到风险等级为高的节点时,则以节点操作的变量的数据流为导向,提取AST中从root节点开始,该变量流经的所有节点和边作为污点子树。

当检测到风险等级为中的节点时,同样以节点操作的变量的数据流为导向,提取从root节点开始,该变量数据流到达该中风险等级节点之前的属于该数据流中的所有节点和边,并从该风险节点开始往下流经的所有支路节点和边作为污点子树。

当检测到风险等级为普通的节点时,仅提取当前节点和变量数据流流经的支路节点和边作为污点子树。

语义分析模块

这部分是作者写的最模糊的地方,为了文章叙述的完整考虑,我把这些疑问以及自己的思考单独放在了文章结尾。

语义分析模块使用图匹配算法,通过行为特征图对经过标准化处理的污点子树对象进行匹配值计算。

行为特征图来自于预置的风险特征库中的特征,其中每个特征表示了一个恶意行为的图建模,每个特征图由如下元素组成:

G x G_x Gx = { F e n t e r F_{enter} Fenter, F e n d F_{end} Fend, F [ 1 , 2 , . . . , n ] F_{[1,2,...,n]} F[1,2,...,n], C C C, D D D}

F e n t e r F_{enter} Fenter为图的入口节点, F e n d F_{end} Fend为图的出口节点, F [ 1 , 2 , . . . , n ] F_{[1,2,...,n]} F[1,2,...,n]为图中的所有单行为集合,C为控制依赖边,表示各节点的调用顺序,D为数据依赖边,表示污染变量的传递。

每个单行为代表抽象语法树种的一个节点,由以下三元组构成

F x F_x Fx = { E x p r Expr Expr, p i n p_{in} pin, p o u t p_{out} pout}

E x p r Expr Expr为节点名称,即节点风险评估表中第一列的内容。 p i n p_{in} pin为节点的输入参数, p o u t p_{out} pout为节点的输出参数。

所以,风险特征库则是一系列特征图的集合:

T T T = { G 1 G_1 G1, G 2 G_2 G2, …, G n G_n Gn}

至于如何构建风险特征库,可以采用提取污点子树的方式,在webshell样本中提取污点子树,只保留确认是有害行为的污点子树作为特征图即可。

该模块的作用既是将提取出的污点子树与特征库中的图特征做匹配,匹配算法如下:

E G x E_{G_x} EGx = ∑ i = N , C , D ∑ j = 0 n i x i j ∗ p i j ∑ i = N , C , D X i ∗ P i {\sum\limits_{i=N,C,D} \sum\limits_{j=0}^{ni}x_{ij}*p_{ij}} \over {\sum\limits_{i=N,C,D}X_{i}*P_{i}} i=N,C,DXiPii=N,C,Dj=0nixijpij

E G x E_{G_x} EGx为匹配过程计算出的特征图匹配值, x i j x_{ij} xij为在各种点和边类型上的命中数, p i j p_{ij} pij为相应的权值(每个名称的点以及连接不同名称点的边都需要预设相应的权值), N N N为节点集合, C C C为控制依赖边集合, D D D为数据依赖边集合, X i X_i Xi为一个图特征中点和边各自的数量, P i P_i Pi为平均权值( N , C , D N,C,D N,C,D中各自点或边的所有权值的平均值)。

点的匹配好理解,只需匹配AST节点的名称即可,而边的匹配需要同时匹配出节点和入节点的节点名称。

通过预设定匹配值阈值,对输出的匹配值进行选择,匹配值低于阈值则判断为安全,高于阈值判定为风险文件。

好了,以上就是整体的检测思路。

疑问

在语义分析模块部分的所有疑问我都列在了下面:

  1. 风险特征库中特征图的构成元素中有一个出口节点,而如果把每个叶子节点作为出口节点,那这里的出口节点应该是一个集合才对,为什么只有一个节点来表示出口节点呢?
  2. 一个特征图中会有一个单行为集合,每个单行为包括AST节点的输入参数和输出参数,但语法树上的节点没有输入参数和输出参数这两个属性,那这两个参数又是哪里来的呢?
  3. 图匹配过程只匹配了节点的类型,而scalar标量类型节点内包含的变量名(比如$_GET、$_POST这些外部输入变量)也是不可或缺的webshell判断特征,只匹配类型势必会造成误报,如果也加入值的匹配是不是更严谨一点呢?

这些疑问我思考了一段时间也没有想出答案,作者给出了一个阉割版的方案,那我们也只能阉割式的使用它了。

针对各个疑问,我的想法是这样的:

  1. 去掉特征图组成元素中的出口节点,甚至入口节点都可以去掉,关键信息都由单行为集合、控制边和数据边描述了,这两个元素也显的无关紧要了;
  2. 去掉输入参数和输出参数,实在没想明白这两个参数的由来,而且它们在匹配过程中没有发挥任何作用;
  3. 在scalar类型的节点中加入值属性,匹配过程不仅匹配节点名称,针对scalar类型的节点也要匹配节点值,名称和值都匹配上才算这个节点匹配上;

总结

作者整体的思路还是比较新颖的,充分考虑了复杂webshell污染变量传递的特点,确实也能完善正则表达式对上下文语义理解的不足,也算是在正则匹配检测基础上的一种改进吧。

但webshell检测之所以难的地方就在于存在大量的变形和混淆方式,在针对变形和混淆上的检测,语义检测作为静态检测方式是有很大的局限性的。

包括有些论文也论述了使用朴素贝叶斯和SVM等这些传统机器学习方法来构建核心检测逻辑的,但实际运行的结果非常令人失望,超高的误报率让它无法被应用到生产环境中。我们公司团队也曾尝试过使用CNN卷积神经网络来深度发掘webshell的本质特征,最终也是以失败告终。

所以,我可以断言,新一代的webshell检测一定是动态执行+静态分析结合的方式,通过动态执行还原出webshell的核心恶意点,再辅以静态分析做结果的处理和展示。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值