WebShell 工具流量特征

上一讲从文件系统找 WebShell,但这套方法有个硬边界:很多运营时间不长的后门已经被删了,内存马更是根本不落地为普通文件。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:Behinder、Godzilla、蚁剑、中国菜刀、NhShell、AES-CBC 载荷、协议指纹、access_log 分析 难度:进阶 前置知识:HTTP 协议、Nginx/Apache combined 日志格式(见第 05 篇)、Base64 与 AES 基本概念


⚖️ 本篇的边界(重要)

本篇只从防御与取证角度讨论 WebShell 管理工具的通信协议特征与检测方法。目的是让取证人员能够看懂日志、识别工具、还原行为、关联攻击者。

本篇不包含:WebShell 的构造方法、免杀与变形技术、加密绕过手法、上传与落地技巧、载荷构造细节。这些内容不属于取证知识,且会直接降低防御方的识别能力。

文中出现的协议描述、字段特征、检测规则,仅用于解释"如何在日志与流量中认出这些工具"。凡涉及具体实现的部分,均以"如何检测"而非"如何实现"的角度表述。

一、概述

磁盘上找不到后门,不等于入侵没发生过。

上一讲从文件系统找 WebShell,但这套方法有个硬边界:很多运营时间不长的后门已经被删了,内存马更是根本不落地为普通文件。

Behinder 这类工具的 Behinder.jar 被加载进 JVM 内存,磁盘扫描可能一无所获。这时候唯一能还原行为的线索只剩流量与日志。

WebShell 管理工具的共性是:通信比正常业务"更像机器"。

正常用户浏览网站,路径五花八门、Referer 层层传递、UA 变化丰富。而工具客户端与后门之间是程序对程序的固定对话——路径长期固定、频率机械等间隔、Referer 恒为 -、请求体是 Base64 或二进制密文。

这种"过于规整"就是本篇的抓手。

在取证流程中的位置:本篇属于分析环的行为重建层,与文件层互为补充而非替代。

它的独特价值在两个场景。

一是唯一证据场景(文件已删、内存马不落地)。

二是交叉验证场景(文件层发现可疑文件后,用日志确认"谁访问过、访问了几次、返回什么状态")。

上游依赖 Nginx 与 Apache 配置取证 提供的 log_format 实际定义与轮转清单;下游支撑攻击链还原(第 5 步)与报告里的归因表述。

涉及的证据形态(具体到字段,不是"流量"这种大类):

形态 位置 可观测特征
访问日志 /www/wwwlogs/<site>.log*、含 .gz 轮转历史 combined 的 9 个字段,须先核对实际 log_format
URL 参数 查询串里的长 Base64 串(q=、key=、pass=) 最强信号——Base64 出现在正常查询串里极少见
路径构造 /static/img/../upload/xxx.php 静态目录 + .. 跳出 + 文件名本身是 Base64
请求体长度 需从流量侧或应用层日志取 16 的倍数(AES 分组对齐)、少数几个固定值
响应体长度 combined 的 $body_bytes_sent 固定在几百字节到 2 KB,不随内容变化
Referer / UA 日志末两段 恒为 -;Java/1.8.0_281、Go-http-client 等非浏览器串
技术栈矛盾 纯 PHP 站点出现 .jsp 命中数与状态码分布是判据
蜜罐与诱捕 主动部署的诱捕路径 访问即说明有人在扫,访问者 IP 高价值

🔴 一个必须先建立的观念:单个特征几乎必然有误报。 固定路径可能是健康检查,机械频率可能是定时任务,Base64 可能是正常 API。所以本篇的脚本输出是线索排序,不是判决——见 4.2 统计方法 与 4.4 归因表述。

能回答什么:哪个 (IP, 路径) 组合的行为最不像正常业务;某组请求的次数、间隔标准差、请求体长度聚集度、Referer 缺失率分别是多少;可疑 IP 是否在别处也留下过痕迹;某时段的行为属于"写入后验证"还是"持续使用";报告里应该把结论写到哪一档强度。

不能回答什么:请求体和响应体的内容——combined 日志里没有这两项。这是本篇最常被误解的一点,见 4.1 字段语义。

对方具体执行了什么命令?只能说"符合某工具的协议特征",不能说"执行了某命令"。

明文密钥、加密参数推导、载荷构造细节——这些属于攻击实现,不在取证知识范围内。

被 access_log off 关闭的 location 下的行为?同样查不到。

内容边界(本篇的红线,已在篇首声明):本文只从防御与取证角度讨论通信协议的可观测特征与检测方法,目的是让取证人员看懂日志、识别工具、还原行为、关联攻击者。不包含 WebShell 构造方法、免杀与变形技术、加密绕过手法、上传与落地技巧、载荷构造细节。凡涉及具体实现的部分,均以"如何检测"而非"如何实现"的角度表述。

读者前提:需要 HTTP 请求/响应结构与 combined 日志的完整字段顺序(先读 第 1 篇 的 2.2 节)、Base64 与 AES 的分组对齐概念、以及基本的统计量含义(标准差、变异系数)。

日志量到百万行级别时手工翻查不现实——这正是 3.4 第 4 步 脚本存在的理由。

建议先做 3.3 的人工粗筛,再跑脚本。

二、核心原理

2.1 主要工具与通信模型概览

国内常用的 WebShell 管理工具,通信模型可归为四类:

工具 通信模型 载荷加密 取证要点
中国菜刀(Caidao) 单文件后门,客户端直连 早期明文;部分版本对请求体做简单混淆 请求体短、路径固定、POST 为主;特征在"一个文件承载全部功能"
蚁剑(AntSword) 单文件后门,客户端直连 支持 AES、RC4 等 载荷加密参数常通过 URL 传密钥;同 URL 大量 POST
Behinder(冰蝎) 双文件架构:server.php(正常业务文件)+ Behinder.jar(内存马) AES-CBC 载荷,密钥在 URL 路径中协商 ★ 见 2.2
Godzilla(哥斯拉) 单文件或双文件,支持 JSP/PHP/ASPX AES,长密钥经 URL 传参 ★ 见 2.3
NhShell 单文件后门 自定义编码 请求体非明文、常无 Referer
rederb / Cobalt Strike 等 Stager 型内存马 加密信道 出现在 .jsp/.asp 但服务器是 PHP,或反之

双文件架构为何重要(取证意义):Behinder 这类工具的 Behinder.jar 不落地为普通文件——它被加载进 JVM 内存。所以磁盘扫描可能一无所获,只有流量和内存镜像能证明它存在。这正是本篇存在的意义。

2.2 Behinder 的通信特征(重点)

Behinder 采用 AES-CBC 加密的请求体 + 路径内携带密钥 的模型。

以下从"如何在日志中识别"的角度说明。

结构一:URL 路径携带加密参数

POST /static/img/../upload/aGFsbG8gd29ybGQ.php?q=LEQI1r8CAg1pVQ1WVA1WC1JVA1WC1JVA1WQ1WS1JQ1JVZQ%3D%3D HTTP/1.1
观察点 说明
路径中出现很长的 Base64 串(常见于 q、key、pass 等参数名) 密钥/校验串;参数名可被工具配置改变
路径里用 .. 从静态目录跳到可写目录 如 /static/img/../upload/webshell.php——混在静态目录里
出现 .. 但最终未跳出站点根 典型的"藏在看似静态目录下"

结构二:请求体是 Base64 包装的 AES-CBC 密文

U3VjbiB2ZXJzaW9uOiAxLjcuMzA=        ← Base64 解码后是明文

日志中(combined 格式)的体现:

198.51.100.24 - - [18/Mar/2024:02:14:33 +0800] "POST /static/img/../upload/webshell.php?q=LEQI...%3D%3D HTTP/1.1" 200 1548 "-" "Java/1.8.0_281"

识别要点:

特征 说明
URL 里出现长 Base64 参数 最强信号。Base64 出现在正常 URL 查询串里极少见
请求体长度为 16 的倍数(Base64 后通常是 4 的倍数) AES 分组对齐的结果
同一 URL 的请求体长度呈少数几个固定值 密文长度由明文长度决定,命令种类有限 ⇒ 长度高度聚集
响应体长度固定在几百字节到 2 KB 客户端回显结果,不像正常业务那样随内容变化
无 Referer 客户端直接构造请求,不可能有站内跳转链
UA 为空或为 Java/Go/Python 客户端串 不是浏览器

这六条里,长 Base64 参数权重最高。其他几条单独看都能找到正常解释,凑齐了才成立。

取证边界:本篇只说明"密文长度呈 16 倍数、URL 含长 Base64"这类可观测特征。密钥协商的具体字段、加密算法参数的完整推导,属于攻击实现细节,不在取证知识范围内,也不需要在报告里还原——报告只需写明"该通信符合 Behinder 类工具的协议特征"。

2.3 Godzilla 的通信特征

Godzilla 支持多种后门形态与多种加密方式。

取证上有用的共性:

特征 说明
密钥在 URL 查询参数中传递 参数名常见为 pass、key、q、p(可配置,不可依赖固定名)
URL 参数值很长且是合法 Base64/十六进制 ★ 核心信号
同 URL、高频 POST、请求体固定长度 交互式会话特征
支持 JSP/ASPX 后门 ★ 若服务器是纯 PHP 站点,日志里却出现 .jsp / .asp / .aspx 请求且返回 200 —— 高度异常

"技术栈与请求路径矛盾"是一条非常好用的规则:

站点是纯 PHP(Nginx + PHP-FPM),但 access_log 里出现:
  198.51.100.24 POST /upload/cmd.jsp HTTP/1.1  →  200

这不是扫描(扫描通常返回 404/403),而是 200。

200 意味着服务端真的执行了它——要么站点混装了 Java 容器,要么服务端做了路径伪装映射,要么是另一个后门在响应。

这个矛盾点必须逐个核实。

2.4 流量层的通用检测维度

以下是不依赖具体工具的通用规则,适用于所有 WebShell 运营行为:

维度 检测方法 权重
路径集中度 单一 URL 占比(按 IP 分组统计) ★★★★★
请求体长度方差 同一 IP+URL 的 POST 长度标准差 ★★★★★
无 Referer 比例 # Referer 为 - 的占比 ★★★★
访问时间规整度 相邻请求间隔的变异系数 ★★★★
UA 异常 为空、非浏览器串、与其他流量截然不同 ★★★
状态码组合异常 同一 URL 只有 200 很少有 404("写后必验"特征) ★★★★
技术栈矛盾 PHP 站点出现 .jsp/.asp 且返回 200 ★★★★★

"写后必验"是一个高价值模式:

正常用户访问一个不存在的 URL 会收到 404,访问存在的会收到 200,两种都常见。

而 WebShell 场景常见的是:同一路径在极短时间内先 404(探测)后 200(写入后验证),或者长期只出现 200 而从无 404(说明攻击者只用自己的固定路径,不做探索)。

2.5 日志层:必须知道的三条限制

在写检测规则前,必须先认清 Nginx/Apache 日志的天花板:

限制 后果 应对
默认不记录请求体 看不到 AES 密文、指令内容、上传的文件 只能从长度、频率、路径推断;结论必须写明这一限制
默认不记录响应体 看不到回显的命令结果 同上
$body_bytes_sent 是响应体大小,不是请求体大小 ⚠️ 最常见的误读:有人拿它当"上传大小"分析 请求体大小只能从 $request_length(若自定义了 log_format)或上游日志拿
可能没有 $request_time / $request_length 无法做耗时与请求体大小分析 先 grep log_format 确认字段
可能 access_log off 该 location 完全无记录 见第 05 篇

🔴 报告中必须写明:access_log 不含请求体,因此"该请求下发了什么指令"无法从本证据确定。这不是分析不到位,是证据本身的边界。夸大结论是取证报告最严重的缺陷。

2.6 蜜罐与诱捕后门的取证价值

在被入侵的服务器上部署"诱捕后门",是被动获取情报的经典手法。

其取证价值在于:

价值点 说明
获取密钥材料 攻击者会把自己的加密密钥、口令、C2 地址写进蜜罐——这是主动上送的证据
还原完整行为 蜜罐可以记录攻击者执行的每一条命令和返回结果,弥补 access_log 无请求体的缺陷
证明攻击意图 攻击者在蜜罐上的操作,主观故意证据非常充分
关联身份 蜜罐可以"回话"给攻击者,取证人员在授权范围内可与其交互以获取更多线索

⚠️ 合规红线:蜜罐/诱捕后门的部署必须事先获得委托方书面授权,且要明确数据留存与上报流程。未经授权自行部署可能构成违法,且会改变现场状态、影响证据完整性。 若案件已进入司法程序,任何"主动交互"都应在委托方与办案机关共同认可下进行。


三、操作步骤

3.1 第 1 步:确认日志格式与可用字段

Z=/mnt/df/www/wwwlogs/digiforensics-site.log

1) 看真实格式(可能不是 combined!)

grep -n 'log_format' /mnt/df/www/server/nginx/conf/nginx.conf 2>/dev/null grep -rn 'log_format' /mnt/df/etc/nginx/ 2>/dev/null

2) 确认有哪些轮转文件

ls -l /mnt/df/www/wwwlogs/ 2>/dev/null

digiforensics-site.log digiforensics-site.log-20240317.gz ...

3) 取样看真实行长什么样

head -3 "$Z"

198.51.100.24 - - [18/Mar/2024:02:14:33 +0800] "POST /a.php HTTP/1.1" 200 1548 "-" "Java/1.8.0_281"

3.2 第 2 步:合并所有轮转日志

Z=/mnt/df/www/wwwlogs/digiforensics-site.log ALL=/evidence/all-access.log

合并并按时间排序(注意:合并前先记录每个文件的哈希)

sha256sum /mnt/df/www/wwwlogs/digiforensics-site.log* | tee /evidence/log-hashes.txt

zcat -f "$Z" /mnt/df/www/wwwlogs/digiforensics-site.log-*.gz 2>/dev/null
| sort -t'[' -k2 | tee "$ALL" | wc -l

3.3 第 3 步:快速人工筛选

在跑脚本之前,先用几条命令把最可疑的挑出来:

Z=/mnt/df/www/wwwlogs/digiforensics-site.log

① 单一 IP 打单一路径的次数最多者(WebShell 运营的典型形态)

awk -F'"' '{split($1,a," "); print a[1], $2}' "$Z"
| awk '{print $1, $2, $3}' | sort | uniq -c | sort -rn | head -20

② URL 里含长 Base64 参数的(Behinder/Godzilla 类)

grep -oE '"[A-Z]+ [^"]*?[a-zA-Z_]+=[A-Za-z0-9%+/=]{24,}' "$Z" | head -20

③ 路径里含 .. 但未跳出站点根

grep -E '"[A-Z]+ [^"]*../' "$Z" | head -20

④ 技术栈矛盾:PHP 站点里的 .jsp/.asp/.aspx 请求

grep -E '.(jsp|asp|aspx)([ ?"]|$)' "$Z" | head -20

⑤ 无 Referer 的 POST 请求(Referer 字段在 "..." 之后)

grep -E '"POST [^"]*" [0-9]{3} [0-9]+ "-" ' "$Z" | head -20

⑥ 非常规 UA 为空的行

awk -F'"' 'NF<6 || $6=="-" || $6==" "' "$Z" | head -20

3.4 第 4 步:可运行的日志分析脚本

下面脚本只读取日志、只做统计、不发起任何连接、不发送任何请求。

#!/usr/bin/env python3 """ log_triage.py —— WebShell 工具流量特征分析(防御侧) 只读日志,输出可疑度排序。不发送任何网络请求,不执行任何内容。 用法: python3 log_triage.py /evidence/all-access.log """ import re import sys import gzip from collections import defaultdict from datetime import datetime from statistics import pstdev

combined 格式:$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$referer" "$ua"

LINE_RE = re.compile( r'^(?P\S+)\s+\S+\s+(?P\S+)\s+' r'[(?P[^]]+)]\s+' r'"(?P[^"]*)"\s+' r'(?P\d{3})\s+' r'(?P\S+)\s+' r'"(?

安全验证 当前请求需要先完成一次滑块验证。