背景
Webshell 相关告警在应急里很常见,但真正影响处置质量的,往往不是“找到一个可疑文件”这么简单,而是能否判断入口、确认影响范围、清理持久化痕迹,并把同类问题收口。这里整理一套偏实战的排查思路,适合用于内部复盘、主机自查和蓝队侧验证。
本文不讨论攻击利用细节,也不提供可直接用于入侵的操作。重点放在日志分析、文件取证、代码审计和加固验证上。
典型场景
某业务系统出现 Webshell 告警,安全设备给出的证据是一个新创建的脚本文件,路径位于站点上传目录。业务侧反馈近期没有发布,服务器上也没有明显异常进程。此类场景里,常见风险点包括:
- 上传接口缺少类型校验,导致脚本文件落到 Web 可访问目录。
- 历史漏洞被利用,例如编辑器、CMS 插件、旧版框架组件。
- 已存在的 Webshell 二次落地,或通过计划任务、包含文件维持访问。
- 误报,例如缓存文件、模板文件中存在高危函数特征。
排查思路
我的习惯是先固定证据,再判断入口,最后做收口验证。不要一上来直接删除文件,否则容易丢掉时间线和关联证据。
一、保留现场与建立时间线
先记录文件基本信息,包括路径、权限、属主、大小、哈希、创建时间、修改时间、访问时间。Linux 下可以这样做:
stat /path/to/suspicious.php
sha256sum /path/to/suspicious.php
ls -lah --time-style=full-iso /path/to/suspicious.php
如果是整站排查,可以按最近修改时间快速拉一遍清单:
find /var/www/html -type f -mtime -7 -printf '%TY-%Tm-%Td %TH:%TM:%TS %u %g %m %p\n' | sort
需要注意,文件时间戳可能被修改,不能单独作为结论。它更适合作为线索,用来和 Web 访问日志、错误日志、系统认证日志做交叉验证。
二、识别可疑文件特征
Webshell 不一定都有明显的 eval、assert、system 等特征,实际环境中也常见混淆、分段拼接、动态调用。排查时可以从几个角度入手:
- 站点目录中出现业务不认识的脚本文件,尤其是上传目录、缓存目录、临时目录。
- 文件名伪装成图片、日志、缓存,例如 .php、.phtml、.phar 等脚本扩展被放在 uploads 下。
- 文件内容存在异常编码、超长单行、变量函数调用、字符串拼接执行。
- 同目录出现最近新增的配置文件,例如 .user.ini、.htaccess,改变了解析规则。
可以用 grep 做初筛,但不要把结果直接等同于 Webshell。比如一些框架、模板引擎也会使用类似函数。
grep -RIn --exclude-dir=vendor --exclude-dir=node_modules \
-E 'eval\s*\(|assert\s*\(|base64_decode\s*\(|gzinflate\s*\(|shell_exec\s*\(|passthru\s*\(|proc_open\s*\(' \
/var/www/html
对于 PHP 站点,还要特别关注自动包含和解析配置:
find /var/www/html -name '.user.ini' -o -name '.htaccess' -o -name 'php.ini' -print
这类文件可能用于改变运行时行为。排查时重点看 auto_prepend_file、auto_append_file、AddType、SetHandler 等配置项是否异常。
三、从访问日志反推入口
确认可疑文件后,不要只看它被访问的记录,还要看它创建前后有哪些异常请求。常见入口包括上传接口、编辑器组件、后台管理接口、历史漏洞路径等。
可以先按文件名搜索访问记录:
grep -R "suspicious.php" /var/log/nginx/access.log* /var/log/httpd/access_log* 2>/dev/null
再围绕文件时间点前后 30 分钟到 2 小时,筛选异常状态码、非常规 User-Agent、POST 请求、上传接口访问:
zgrep -h "POST" /var/log/nginx/access.log* | grep -E "upload|file|editor|admin|api"
如果日志格式包含请求耗时、请求体大小、响应大小,也可以把这些字段纳入分析。一次成功上传通常会有比较明确的请求体大小变化;漏洞扫描则常伴随大量 404、403、500。
四、检查权限与执行面
有时问题不是某个漏洞本身,而是站点目录权限过宽,导致低权限业务进程可以写入可执行目录。建议检查:
- Web 进程用户是否拥有站点代码目录写权限。
- 上传目录是否被配置为禁止脚本执行。
- 临时目录、缓存目录是否暴露在 Web 访问路径下。
- 是否存在可写的包含目录,导致文件包含链路风险。
Nginx 场景下,可以对上传目录禁用脚本解析,例如:
location ^~ /uploads/ {
location ~* \.(php|phtml|phar|php5)$ {
return 403;
}
}
Apache 场景可以在上传目录放置受控的配置,禁止脚本执行。实际生产中更推荐从主配置层面统一限制,避免被目录级配置覆盖。
五、代码审计关注点
如果日志显示入口指向上传、导入、模板编辑、附件管理等功能,需要回到代码层面确认根因。审计时建议关注以下点:
- 是否仅依赖前端校验文件类型。
- 后端是否只检查 Content-Type,而没有校验真实文件内容和扩展名。
- 是否允许用户控制保存路径、文件名、扩展名。
- 是否存在解压缩上传,导致目录穿越或危险扩展落地。
- 是否将上传文件保存到可被脚本引擎解析的目录。
一个较稳妥的上传处理方式通常包括:白名单扩展名、重新生成文件名、限制大小、限制 MIME、必要时做文件头校验、保存到非执行目录、通过对象存储或下载接口间接访问。
需要强调,黑名单扩展名不可靠。除了常见脚本扩展,还会受到服务器解析配置、框架路由、历史兼容规则影响。
六、排查持久化与横向影响
Webshell 清掉以后,还要确认是否存在后续动作。至少建议检查以下内容:
- 计划任务:crontab、/etc/cron.*、systemd timer。
- 异常进程:Web 用户启动的 shell、下载器、脚本解释器。
- 新增账户、SSH 公钥、sudoers 变更。
- 站点代码被植入的包含语句或后门逻辑。
- 数据库中是否有异常管理员、异常模板内容、异常配置项。
一些基础命令可以辅助确认:
ps aux --sort=-%cpu | head
ps aux | grep -E 'www-data|apache|nginx|php-fpm'
crontab -l
ls -lah /etc/cron.d /etc/cron.daily /etc/systemd/system
last -a | head
如果业务允许,建议对站点代码与可信版本做 diff。比起肉眼扫目录,版本对比更容易发现小范围植入。
处置建议
- 先隔离风险入口,例如临时关闭上传接口、限制后台访问来源、下线异常插件。
- 备份可疑文件和关联日志,再进行清理,不要直接覆盖现场。
- 修复根因后再恢复业务,否则容易反复落地。
- 轮换相关凭据,包括后台账号、数据库账号、对象存储密钥、部署密钥。
- 补齐上传目录禁止执行、最小权限、日志留存、文件完整性监控。
容易忽略的细节
只删除 Webshell 不等于完成处置。真正要回答的问题是:它怎么进来的、是否被访问过、访问后做了什么、同类入口是否还存在。
- 日志留存时间太短会严重影响溯源,建议至少保留 30 到 90 天,关键系统更长。
- 反向代理、负载均衡后面要保留真实源 IP,否则只能看到代理地址。
- 多台 Web 节点要一起查,单节点清理没有意义。
- 容器环境还要检查镜像、挂载卷、CI/CD 凭据,避免下次发布又带回问题。
- 安全设备告警只是一条线索,最终结论要结合文件、日志、代码和业务变更记录。
总结
Webshell 应急的核心不是“特征查杀”,而是围绕时间线做证据闭环。先固定现场,再定位入口,随后确认影响范围和持久化痕迹,最后通过代码修复、权限收敛和日志监控完成闭环。
如果要把这类经验沉淀为长期能力,建议把常见排查命令、日志字段说明、上传安全基线、应急检查清单固化到团队文档里。下次遇到类似告警时,处理效率和结论可信度都会高很多。
推荐意见