跳转到帖子

背景

WebShell 排查是应急响应里很常见的一类工作,但真正落地时经常会遇到两个问题:一是告警信息很少,只有几条异常访问日志;二是业务目录复杂,上传目录、缓存目录、插件目录混在一起,单纯按文件名搜索很容易漏掉。

这篇文章整理一次偏通用的排查思路,重点放在如何从访问日志、文件时间线、代码特征和运行痕迹逐步缩小范围。内容只讨论防守侧定位、取证和加固,不涉及攻击利用或控制操作。

问题场景

某站点出现异常外连告警,出口设备记录到 Web 服务器在夜间访问了多个陌生 IP。初步看业务仍可访问,主机没有明显高负载。管理员提供的信息比较有限:

  • 站点使用 PHP 部署,包含后台上传功能。
  • Nginx 反向代理到 PHP-FPM。
  • 最近一周做过插件升级,也有运营人员上传过附件。
  • 告警时间集中在凌晨 2 点到 4 点。

这类场景不要一开始就全盘删除文件。建议先保留现场,复制日志和可疑文件,避免破坏时间戳和后续分析线索。

第一步:固定现场与基础信息

先做只读式的信息收集,记录系统时间、进程、网络连接、定时任务和 Web 目录基本情况。下面的命令偏排查视角,执行前应结合生产环境权限和变更规范。

date
whoami
hostname
uptime

ps aux --forest
ss -antp

crontab -l
ls -al /etc/cron* /var/spool/cron 2>/dev/null

find /var/www -maxdepth 3 -type f -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort | tail -n 50

这里重点看三个点:

  • 告警时间附近是否有异常进程或外连连接。
  • Web 目录最近是否新增 PHP、phtml、phar、inc 等可执行脚本。
  • 定时任务里是否出现不符合业务习惯的脚本路径。
经验上,WebShell 不一定放在上传目录,也可能藏在模板缓存、插件目录、语言包目录、备份目录里。只看 upload 往往不够。

第二步:从访问日志还原入口

访问日志是排查 WebShell 的核心线索。先按告警时间窗口筛选请求,再观察请求方法、状态码、响应体大小、User-Agent 和 Referer。示例以 Nginx 日志为例:

# 按时间粗筛
awk '$4 >= "[10/Mar/2025:02:00:00" && $4 <= "[10/Mar/2025:04:30:00" {print}' /var/log/nginx/access.log > /tmp/suspect_access.log

# 统计高频访问路径
awk '{print $7}' /tmp/suspect_access.log | sort | uniq -c | sort -nr | head -n 30

# 关注 POST 请求
awk '$6 ~ /POST/ {print}' /tmp/suspect_access.log | head -n 50

# 关注非典型脚本后缀
 grep -Ei '\.(php|phtml|phar|inc)(\?| |$)' /tmp/suspect_access.log | head -n 100

排查时可以优先关注以下特征:

  • 冷门路径突然被 POST 访问,例如缓存目录、图片目录下的脚本。
  • 同一 IP 对多个类似文件名进行探测。
  • 响应状态码为 200,响应体很小但请求频繁。
  • User-Agent 为空、异常短,或者明显不是正常浏览器。
  • 请求参数包含长 Base64、十六进制编码、大量特殊字符。

如果日志格式记录了请求体长度或上游响应时间,也可以结合分析。WebShell 操作时常见的特征是请求不一定多,但每次响应耗时和响应大小会随命令结果变化。

第三步:建立文件时间线

仅凭文件名判断风险不可靠,时间线通常更有价值。把 Web 根目录内最近变更文件按时间排序,再和访问日志对齐。

# 最近 7 天修改过的脚本文件
find /var/www -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' -o -name '*.inc' \) -mtime -7 \
  -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %p\n' | sort

# 最近 7 天新增或修改的所有文件,便于发现伪装文件
find /var/www -type f -mtime -7 -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %p\n' | sort > /tmp/www_recent_files.txt

需要注意,攻击者可能会修改 mtime,不能只信一个时间字段。Linux 文件系统通常还可以看 ctime,虽然它也不是完整取证意义上的审计记录,但在现场排查里有参考价值。

stat /var/www/html/path/to/suspicious.php

如果业务有发布系统或 Git 版本管理,可以把文件与基线比对。没有基线时,至少可以和同版本安装包、备份包做哈希比对。

# 生成当前 Web 目录脚本文件哈希
find /var/www/html -type f \( -name '*.php' -o -name '*.inc' \) -print0 \
  | xargs -0 sha256sum > /tmp/current_php.sha256

第四步:代码特征筛查

WebShell 的代码形态差异很大,但在 PHP 场景中,仍然可以通过危险函数组合、动态调用、编码混淆等特征做初筛。注意,特征命中不等于一定恶意,很多框架和插件也会使用反射、序列化、压缩编码,需要人工复核。

# 常见危险函数和动态执行特征
grep -RInE 'eval\s*\(|assert\s*\(|system\s*\(|shell_exec\s*\(|passthru\s*\(|proc_open\s*\(|popen\s*\(|`[^`]+`' /var/www/html --include='*.php'

# 编码、压缩和动态变量特征
grep -RInE 'base64_decode\s*\(|gzinflate\s*\(|str_rot13\s*\(|hex2bin\s*\(|\$\$|call_user_func\s*\(' /var/www/html --include='*.php'

# 关注上传目录中的可执行脚本
find /var/www/html -type f \( -path '*upload*' -o -path '*uploads*' -o -path '*attachment*' \) \
  \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' \) -print

人工复核时,我一般会看以下几点:

  • 文件路径是否符合业务逻辑,例如图片目录里出现 PHP 脚本就很可疑。
  • 文件头部是否缺少正常项目注释、命名空间、框架引入。
  • 是否存在从请求参数、Cookie、Header 读取数据后动态执行的链路。
  • 是否有大量无意义变量名、字符串拼接、分段解码。
  • 是否与正常版本文件相比多出少量代码片段,尤其是文件尾部追加。

对于可疑文件,不建议直接在线打开执行路径测试。应先复制到隔离环境,按文本方式查看,必要时用容器或离线虚拟机做静态分析。

第五步:确认落点与访问链路

当发现可疑脚本后,需要把证据链串起来,而不是只删除文件。一个较完整的确认链路通常包括:

  • 文件创建或变更时间与异常访问时间相近。
  • 访问日志中存在该文件的请求记录,且请求来源、方法、参数异常。
  • 代码中存在从外部输入到危险函数的调用链。
  • 主机外连、进程或临时文件与访问时间对应。
  • 上游漏洞或入口可以解释该文件如何写入。

如果日志里能看到上传接口在可疑文件出现前被访问,应重点检查上传校验逻辑和保存路径。例如:

  • 是否只校验前端扩展名,没有服务端校验。
  • 是否允许双扩展名、大小写绕过、特殊后缀。
  • 是否把用户可控文件保存到可执行目录。
  • 是否存在插件安装、模板编辑、后台文件管理等高权限写文件功能。

第六步:处置与加固

处置建议按“隔离、备份、清理、修复、监控”的顺序进行。不要只删 WebShell,否则入口仍在,很快可能再次出现。

  • 短时间隔离受影响站点或限制管理后台来源 IP。
  • 备份可疑文件、相关日志、进程信息和时间线。
  • 清理确认恶意的脚本、异常定时任务、未知账号和临时文件。
  • 修复上传、插件、后台弱口令、已知组件漏洞等入口问题。
  • 轮换后台密码、数据库密码、应用密钥和相关 Token。
  • 补充 WAF、EDR 或日志告警规则,持续观察是否复发。

Web 目录权限也要收紧。原则是上传目录只允许写入静态资源,不允许脚本执行;应用代码目录由发布流程写入,运行用户不应具备随意修改代码的权限。

# Nginx 示例:禁止上传目录执行 PHP
location ~* ^/uploads/.*\.(php|phtml|phar)$ {
    deny all;
}

# 或者对上传目录直接关闭脚本解析,具体路径按实际业务调整
location /uploads/ {
    location ~ \.php$ {
        deny all;
    }
}

PHP 配置层面可以结合业务情况禁用高风险函数,但这不是根治手段,兼容性也需要测试。

; php.ini 示例,需结合业务验证
disable_functions = system,shell_exec,passthru,exec,proc_open,popen

; 限制临时目录和访问路径也可以降低风险面
open_basedir = /var/www/html:/tmp

常见误区

  • 只按文件名查杀:很多 WebShell 文件名会伪装成正常业务文件,甚至直接追加到合法文件中。
  • 只删除不留证:删除后很难判断入口、影响范围和是否有横向痕迹。
  • 只看上传目录:模板、缓存、插件、备份目录同样常见。
  • 忽略访问日志轮转:日志可能很快被覆盖,发现问题后应第一时间复制保存。
  • 把特征命中当结论:安全扫描结果需要结合路径、上下文、调用链和访问记录复核。

总结

WebShell 排查的关键不是记住某几个特征,而是建立稳定的方法:先保留现场,再从日志、文件时间线、代码特征和运行痕迹交叉验证。确认恶意文件后,还要继续追入口、补权限、修漏洞、加监控。

在生产环境里,最有长期价值的改进通常不是某次清理,而是把基线、日志、发布权限和上传目录隔离做好。这样下次出现异常时,可以更快定位差异,也能减少同类问题反复发生。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…