背景
最近整理了一次 Web 服务器可疑文件告警的排查过程,场景并不复杂:业务侧反馈某台 PHP 应用服务器被安全设备提示存在疑似 WebShell 文件,但没有明确说明入口点。类似事件在日常应急里很常见,难点通常不在于“删掉一个可疑文件”,而是要判断它从哪里来、是否被执行过、是否还有持久化或横向痕迹。
这篇文章按一次相对完整的排查链路来写,重点放在防守视角的取证、日志关联、代码审计和加固建议上,不涉及攻击利用细节。
问题场景
已知信息比较有限:
- 服务器为 Linux,运行 Nginx + PHP-FPM。
- 业务目录存在近期新增的可疑 PHP 文件。
- 安全设备提示文件具有 WebShell 特征。
- 业务方不确定是否存在异常登录或数据泄露。
这类场景建议先不要急着删除文件。直接删除会破坏时间戳、文件内容和权限等取证信息,也可能漏掉其他关联文件。更稳妥的做法是先隔离访问、保全证据,再做关联分析。
初始处置:先控风险,再保全
如果业务允许,优先在网关、WAF 或 Nginx 层面临时限制该站点的外部访问,避免可疑文件继续被触发。对于生产环境,通常不建议立即关机,因为关机会丢失部分内存态和进程信息。可以先做以下保全:
mkdir -p /root/incident-$(date +%F)
cd /root/incident-$(date +%F)
# 记录基本系统信息
date > basic.txt
hostname >> basic.txt
whoami >> basic.txt
uname -a >> basic.txt
# 记录进程、网络连接、登录情况
ps auxww > ps_auxww.txt
ss -antup > ss_antup.txt
last -a > last_login.txt
lastb -a > lastb_login.txt 2>/dev/null
w > w.txt
# 记录计划任务
crontab -l > crontab_current_user.txt 2>/dev/null
ls -lah /etc/cron* > cron_dirs.txt 2>/dev/null同时对可疑文件做只读复制,保留哈希:
cp -a /var/www/html/path/suspect.php ./suspect.php.copy
sha256sum /var/www/html/path/suspect.php ./suspect.php.copy > suspect_hash.txt
stat /var/www/html/path/suspect.php > suspect_stat.txt注意不要在原文件上直接使用编辑器打开并保存,避免改变元数据。
技术分析一:确认可疑文件特征
对 WebShell 的初步识别,一般从代码结构、敏感函数、混淆方式和输入来源几方面入手。防守分析中重点不是复现控制效果,而是确认它是否具备远程执行、文件操作、命令执行、信息回显等能力。
常见风险点包括:
- 动态执行:如 eval、assert、preg_replace 的历史危险用法、create_function 等。
- 系统调用:如 system、exec、shell_exec、passthru、popen、proc_open。
- 文件操作:如 file_put_contents、fwrite、unlink、copy、move_uploaded_file。
- 输入来源:如 $_POST、$_GET、$_COOKIE、php://input。
- 混淆痕迹:如 base64_decode、gzinflate、str_rot13、chr 拼接、大量不可读变量名。
# 在业务目录内做敏感函数检索,仅用于排查
cd /var/www/html
grep -RIn --exclude-dir=vendor --exclude-dir=node_modules \
-E "eval\s*\(|assert\s*\(|system\s*\(|exec\s*\(|shell_exec\s*\(|passthru\s*\(|proc_open\s*\(|popen\s*\(|base64_decode\s*\(|gzinflate\s*\(" . > /root/incident-grep.txt检索结果不能直接等同于木马。很多框架、依赖包或运维脚本也可能使用类似函数。因此要结合文件路径、修改时间、代码风格、提交记录和访问日志判断。比如一个位于 uploads 目录下的 PHP 文件,风险通常高于框架核心目录里的正常代码。
技术分析二:从时间线找入口
入侵排查里,时间线非常关键。先看可疑文件的创建、修改时间,再回溯这一时间点前后的访问日志、错误日志和上传目录变化。
# 查看近期变更的 PHP 文件
find /var/www/html -type f -name "*.php" -mtime -15 -printf "%TY-%Tm-%Td %TH:%TM:%TS %p\n" | sort > recent_php_files.txt
# 查看近期上传目录文件
find /var/www/html -type f -mtime -15 -printf "%TY-%Tm-%Td %TH:%TM:%TS %s %p\n" | sort > recent_all_files.txt如果文件系统支持,可以进一步查看 ctime。ctime 不是创建时间,而是 inode 元数据变化时间,但在排查中很有参考价值:
stat /var/www/html/path/suspect.php随后关联 Nginx access log。假设可疑文件修改时间在 10:20 左右,可以提取前后半小时日志:
grep "10/Oct/2026:10:" /var/log/nginx/access.log > access_10h.txt重点关注以下特征:
- 是否有上传接口在可疑时间点被访问。
- 是否有管理后台、插件安装、模板编辑等高风险功能被调用。
- 是否出现异常 User-Agent、非常规 HTTP 方法或大量 4xx/5xx。
- 是否从同一 IP 连续访问上传接口和可疑 PHP 文件。
- 是否存在对配置文件、备份文件、接口文档的探测请求。
如果日志量较大,可以先按状态码、路径、IP 聚合:
# 按 IP 统计访问量
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
# 按 URL 聚合,适合先看异常路径
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -50
# 查看 4xx/5xx 请求
awk '$9 ~ /^[45]/ {print $0}' /var/log/nginx/access.log | head -200技术分析三:排查上传与文件写入链路
WebShell 落地常见原因包括上传校验不足、后台弱口令、组件漏洞、部署目录权限过大、历史测试脚本遗留等。排查时不建议只盯着一个可疑文件,而要把“文件如何写入”作为核心问题。
可以从以下几个方向检查:
- 上传接口是否只校验前端或 Content-Type,而没有服务端白名单。
- 上传后的文件是否保存在 Web 可执行目录下。
- Nginx 是否错误地将 uploads、attachment、public 等目录交给 PHP 解析。
- 后台是否存在文件管理、模板编辑、插件安装等功能。
- 应用日志中是否记录了文件上传、用户登录、后台操作。
一个比较稳妥的 Nginx 加固方式,是禁止上传目录内 PHP 解析:
location ~* ^/(uploads|attachment|static)/.*\.php$ {
return 403;
}如果业务允许,更推荐让上传文件使用对象存储或独立静态域名,并确保该域名不具备脚本解析能力。即使上传校验出现疏漏,也能降低直接执行风险。
技术分析四:检查 PHP-FPM 与权限边界
很多服务器被入侵后影响扩大,并不是单个漏洞本身多严重,而是运行权限过大。例如 PHP-FPM 使用高权限用户运行、业务目录整体可写、配置文件包含数据库明文密码且权限过宽。
建议检查 PHP-FPM 运行用户:
ps aux | grep php-fpm | grep -v grep
cat /etc/php-fpm.d/ 2>/dev/null | grep -E "^(user|group)\s*="再检查 Web 目录权限:
find /var/www/html -type d -perm -0002 -ls > world_writable_dirs.txt
find /var/www/html -type f -perm -0002 -ls > world_writable_files.txt
find /var/www/html -type f -name "*.php" -perm -0200 -ls | head -100原则上,代码目录不应该被 Web 运行用户随意写入。确实需要写入的目录,如缓存、日志、上传目录,应尽量单独隔离,并禁止脚本执行。
技术分析五:判断是否存在后续动作
确认 WebShell 后,还需要判断是否被访问过、是否执行过异常命令、是否留下持久化。可以从以下位置排查:
- Nginx access/error log:是否请求过可疑文件。
- PHP-FPM slow log/error log:是否有异常报错或慢请求。
- 系统认证日志:是否有异常 SSH 登录、sudo 行为。
- 计划任务:是否新增异常任务。
- 启动项:systemd、rc.local、profile 等是否被修改。
- 临时目录:/tmp、/var/tmp、/dev/shm 是否有异常文件。
# 查找临时目录近期文件
find /tmp /var/tmp /dev/shm -type f -mtime -15 -printf "%TY-%Tm-%Td %TH:%TM:%TS %s %p\n" 2>/dev/null | sort
# 查看系统登录与认证日志,路径按发行版不同可能有差异
grep -Ei "Accepted|Failed|sudo|session" /var/log/auth.log 2>/dev/null | tail -300
grep -Ei "Accepted|Failed|sudo|session" /var/log/secure 2>/dev/null | tail -300
# systemd 服务变更排查
find /etc/systemd/system /usr/lib/systemd/system -type f -mtime -30 -ls 2>/dev/null如果发现 Web 运行用户发起异常外连,也需要结合 ss 结果、进程启动时间、父子进程关系进一步确认。对于仍在运行的可疑进程,先记录命令行、环境变量、打开文件,再考虑处置:
# 假设 PID 为 1234
cat /proc/1234/cmdline | tr '\0' ' '
ls -lah /proc/1234/cwd /proc/1234/exe
ls -lah /proc/1234/fd | head
cat /proc/1234/environ | tr '\0' '\n' | head代码审计关注点
如果初步判断入口在业务代码,建议围绕可疑时间点访问过的接口做定向审计,而不是全站盲扫。以文件上传为例,审计重点包括:
- 扩展名校验是否使用服务端白名单,例如仅允许 jpg、png、pdf 等。
- 是否校验真实文件内容,避免只看后缀或 MIME。
- 上传后的文件名是否随机化,是否保留用户原始文件名。
- 是否允许双后缀、大小写绕过、特殊字符路径。
- 上传目录是否与代码目录混放。
- 是否存在任意路径写入、目录穿越或解压缩覆盖问题。
一个更安全的上传处理思路是:服务端白名单扩展名、重新生成文件名、固定存储目录、关闭脚本解析、记录上传审计日志。对于图片类文件,可使用图像库重新编码后再存储,减少夹带非预期内容的风险。
处置与恢复建议
完成证据保全和影响面判断后,再做处置。常见动作包括:
- 删除或隔离确认恶意的文件,并记录哈希和路径。
- 修复入口漏洞,例如上传校验、后台弱口令、组件漏洞。
- 调整目录权限,禁止上传目录脚本执行。
- 轮换数据库、后台、服务器账号等敏感凭据。
- 检查同集群、同镜像、同代码仓库是否存在相同问题。
- 补齐日志留存,避免后续无法追溯。
如果无法确认攻击者是否获得系统权限,建议不要只在原机上“清理一下继续跑”。更稳妥的方式是基于可信镜像重建环境,回滚干净代码和数据,再将修复后的配置上线。
常见误区
- 只删除 WebShell,不找入口。这样很容易复发。
- 只看文件修改时间,不关联访问日志。时间线会断。
- 把 grep 到敏感函数的文件都当木马。容易误判业务代码。
- 让上传目录继续支持 PHP 解析。修复上传校验也不能替代执行隔离。
- 忽略凭据轮换。配置文件、环境变量、日志中都可能暴露敏感信息。
总结
WebShell 告警的排查重点不是单点文件识别,而是围绕“落地、执行、扩展、持久化”建立证据链。实际工作中,可以按这条线推进:先隔离和保全,再确认文件特征,随后用时间线关联访问日志和业务日志,最后修复入口并加固权限边界。
长期看,减少这类事件的关键不只是上设备告警,而是把基础防线做好:上传目录不可执行、代码目录最小权限、后台强认证、组件及时更新、日志可追溯。很多入侵并不依赖复杂技巧,往往是几个基础配置同时失守后被放大。
推荐意见