跳转到帖子

背景

最近整理了一起典型的 WebShell 入侵事件,环境是常见的 Linux + Nginx + PHP-FPM 架构,业务侧使用了一个较老的内容管理系统,并带有自定义上传接口。事件本身不复杂,但覆盖了日志排查、样本分析、入口确认、代码审计和加固验证几个环节,比较适合作为日常应急和审计的参考。

本文只讨论防守视角下的分析方法和修复思路,不涉及利用细节和攻击引导。实际处置时,建议先完整保全现场,再做隔离和清理,避免证据被覆盖。

问题场景

最初的告警来自主机侧 EDR:Web 目录下出现可疑 PHP 文件,文件名类似正常缓存文件,但内容包含动态执行特征。业务反馈近期没有发布操作,也没有新增插件。

现场观察到几个现象:

  • Web 根目录下存在异常 PHP 文件,修改时间集中在凌晨。
  • Nginx 访问日志中出现少量对上传接口的异常请求。
  • PHP-FPM 慢日志没有明显异常,但错误日志中有上传目录权限相关报错。
  • 业务代码中存在历史遗留的文件上传逻辑,没有统一走框架封装。

初步处置原则

这类事件不要上来就直接删除文件。比较稳妥的顺序是:隔离、保全、分析、清理、修复、验证。尤其是 WebShell 文件、访问日志、应用日志、系统审计日志,都可能用于还原入口和影响范围。

# 记录基础信息
hostname
whoami
date
uname -a

# 保全可疑文件元信息,不直接执行
stat /var/www/html/path/to/suspicious.php
sha256sum /var/www/html/path/to/suspicious.php

# 只读方式打包关键目录与日志
mkdir -p /root/incident-backup
cp -a /var/log/nginx /root/incident-backup/nginx-log
cp -a /var/log/php-fpm /root/incident-backup/php-fpm-log
cp -a /var/www/html/path/to/suspicious.php /root/incident-backup/

如果确认存在持续风险,可以先在网络层限制外部访问后台、上传接口和可疑路径,同时不要立刻重启或清空临时目录,避免丢失运行痕迹。

可疑文件分析

样本分析的重点不是复现控制能力,而是识别行为:是否执行动态代码、是否读写文件、是否连接外部地址、是否加载二进制组件、是否窃取配置。对于 PHP WebShell,常见特征包括变量函数调用、编码混淆、危险函数组合、异常字符串拼接等。

可以先用静态方式做基础判断:

# 查看文件类型和大小
file suspicious.php
wc -c suspicious.php

# 抽取可疑关键字,注意不要执行样本
grep -E "eval|assert|system|exec|shell_exec|passthru|popen|proc_open|base64_decode|gzinflate|str_rot13|create_function" suspicious.php

# 查看最近新增或修改的 PHP 文件
find /var/www/html -type f -name "*.php" -mtime -7 -printf "%TY-%Tm-%Td %TH:%TM:%TS %p\n" | sort

如果样本存在多层编码,不建议在生产机上直接还原执行。可以复制到离线分析环境,通过替换危险函数、打印中间变量的方式逐层展开。比如将 eval 替换成 echo,将 system、exec 等函数替换为空实现,只观察最终代码结构。

经验上,WebShell 文件名并不总是随机字符。更常见的是伪装成 cache、config、class、plugin、image 相关命名,放在上传目录、模板缓存目录或插件目录下。

日志关联分析

定位入口时,不要只看 WebShell 的访问日志。更关键的是它创建之前的一段时间内,是否有异常上传、后台接口调用、插件安装、模板编辑、接口报错等行为。

可以围绕文件修改时间向前后扩展 30 分钟到 2 小时进行检索:

# 假设可疑文件修改时间为 2025-01-10 02:14
# 检索该时间段内的上传、后台、插件相关请求
zgrep -E "10/Jan/2025:0[1-3]:|upload|file|plugin|template|admin|editor" /var/log/nginx/access.log* | less

# 查看异常状态码和大请求体
awk '$9 ~ /500|502|403|404/ {print $0}' /var/log/nginx/access.log | less

# 统计高频 URI
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -50

# 统计来源 IP,仅用于排查,不作为唯一归因依据
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -50

如果 Nginx 日志格式包含 request_time、upstream_response_time、body_bytes_sent、http_user_agent,会更容易发现异常。比如某个上传接口在短时间内返回 200,随后出现对新 PHP 文件的访问,这就是比较明确的关联链。

入口代码审计

本次事件最终定位到一个自定义上传接口。代码逻辑大致是:前端限制文件类型,后端只检查扩展名和 Content-Type,然后将文件保存到 Web 可访问目录。这个问题很常见,根因是把“前端校验”和“请求头校验”当成了安全边界。

审计上传逻辑时建议重点看几个点:

  • 是否只依赖文件后缀、Content-Type、前端 JS 判断。
  • 是否允许用户控制保存路径、文件名、扩展名。
  • 上传目录是否位于 Web 可执行路径下。
  • 是否存在双后缀、大小写扩展名、特殊字符截断、解析差异问题。
  • 是否对图片类文件做真实解码和重编码。
  • 是否有鉴权和权限校验,普通用户是否能调用管理接口。

一个更稳妥的上传处理思路是:后端白名单校验、随机文件名、固定扩展名、保存到非执行目录、对象存储或下载代理输出,并且 Web 服务器禁止该目录执行脚本。

# Nginx 示例:禁止 uploads 目录执行 PHP
location ^~ /uploads/ {
    types {}
    default_type application/octet-stream;
    add_header X-Content-Type-Options nosniff;
}

location ~* ^/uploads/.*\.php$ {
    return 403;
}

PHP 侧可以采用更严格的处理方式,下面是简化示例,只表达关键思路:

<?php
$allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$maxSize = 5 * 1024 * 1024;

if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
    http_response_code(400);
    exit('invalid upload');
}

if ($_FILES['file']['size'] > $maxSize) {
    http_response_code(400);
    exit('file too large');
}

$original = $_FILES['file']['name'];
$ext = strtolower(pathinfo($original, PATHINFO_EXTENSION));

if (!in_array($ext, $allowedExt, true)) {
    http_response_code(400);
    exit('extension not allowed');
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
$allowedMime = [
    'jpg' => ['image/jpeg'],
    'jpeg' => ['image/jpeg'],
    'png' => ['image/png'],
    'gif' => ['image/gif'],
    'pdf' => ['application/pdf']
];

if (!in_array($mime, $allowedMime[$ext], true)) {
    http_response_code(400);
    exit('mime not allowed');
}

$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$targetDir = '/data/upload-files/';
$target = $targetDir . $safeName;

if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
    http_response_code(500);
    exit('save failed');
}

echo 'ok';

如果是图片上传,建议进一步使用图像库读取后重新编码输出,避免保留异常附加内容。对于 PDF、Office 等复杂格式,则更适合走独立文件服务,并配合杀毒扫描、内容安全检测和下载鉴权。

权限与目录加固

很多 WebShell 能落地后造成扩大影响,和目录权限配置过宽有关。业务运行用户不应拥有整个站点的写权限,尤其不应能修改代码目录。

可以按目录职责拆分权限:

  • 代码目录:部署用户可写,运行用户只读。
  • 上传目录:运行用户可写,但禁止脚本执行。
  • 缓存目录:运行用户可写,但不要放在可直接访问的执行路径下。
  • 配置文件:运行用户只读,权限尽量收敛。
# 示例:代码目录只读,上传目录单独授权
chown -R deploy:www-data /var/www/html
find /var/www/html -type d -exec chmod 750 {} \;
find /var/www/html -type f -exec chmod 640 {} \;

mkdir -p /data/upload-files
chown -R www-data:www-data /data/upload-files
chmod 750 /data/upload-files

同时建议检查 PHP 危险函数和运行环境隔离。禁用函数不是万能方案,但能降低一部分后利用风险。更关键的是最小权限、禁执行、出网控制和日志可观测。

# php.ini 中可根据业务情况评估禁用
disable_functions = system,exec,shell_exec,passthru,popen,proc_open,pcntl_exec

影响范围排查

入口确认后,还需要判断是否存在横向移动、数据读取、配置泄露等影响。重点检查以下内容:

  • 数据库配置文件是否被访问或复制。
  • WebShell 是否调用过文件读取、命令执行、压缩打包相关能力。
  • 服务器是否新增异常用户、计划任务、SSH key。
  • 是否存在异常出网连接或可疑下载行为。
  • 代码目录是否还有其他后门文件或被篡改文件。
# 最近修改的文件
find /var/www/html -type f -mtime -14 -printf "%TY-%Tm-%Td %TH:%TM:%TS %p\n" | sort

# 可疑计划任务
crontab -l
ls -la /var/spool/cron /etc/cron.*

# 异常用户和 SSH key
cat /etc/passwd
find /home /root -name authorized_keys -type f -print -exec ls -l {} \;

# 网络连接快照
ss -antp

# 常见敏感文件访问痕迹,可结合 access log 和审计日志
zgrep -E "\.env|config|database|backup|\.sql|\.zip|\.tar|\.gz" /var/log/nginx/access.log*

如果日志里发现对数据库备份、配置文件、压缩包的访问,需要进一步评估数据泄露风险。不要只以“WebShell 已删除”作为事件结束标准。

检测规则沉淀

事件处理完后,建议把可复用的检测点沉淀下来。比如 Web 目录新增 PHP 文件、上传目录出现脚本文件、危险函数组合、异常管理接口调用等。

简单的主机侧巡检可以先用脚本实现,后续再接入集中日志或 HIDS:

# 上传目录出现脚本文件
find /var/www/html/uploads -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.phar" \) -print

# Web 目录中含有高风险函数的 PHP 文件
grep -RInE "eval\s*\(|assert\s*\(|shell_exec\s*\(|proc_open\s*\(|passthru\s*\(|base64_decode\s*\(" /var/www/html --include="*.php"

如果使用 YARA,也可以编写偏防守的规则用于发现混淆脚本。规则不要过度依赖单个字符串,否则误报和漏报都会比较明显。

rule Suspicious_PHP_Webshell_Behavior
{
    meta:
        description = "Detect suspicious PHP dynamic execution patterns"
    strings:
        $php = "<?php"
        $s1 = "base64_decode"
        $s2 = "gzinflate"
        $s3 = "str_rot13"
        $s4 = "eval"
        $s5 = "assert"
        $s6 = "shell_exec"
    condition:
        $php and 2 of ($s*)
}

容易忽略的注意点

  • 不要只按文件名查杀。后门可能混在正常业务文件中,或者通过插件、模板、缓存生成。
  • 不要只修上传接口。还要看后台鉴权、插件安装、模板编辑、反序列化、任意文件写入等入口。
  • 不要在生产环境执行可疑样本。静态分析和隔离环境还原更安全。
  • 不要忽视日志留存周期。很多事件发现时已经超过默认日志保留时间,建议至少保留 30 到 90 天。
  • 不要让运行用户拥有代码发布权限。权限边界清晰能显著降低后门持久化风险。

总结

这类 WebShell 事件的核心不在于“删掉一个可疑文件”,而在于确认入口、评估影响、修复根因、建立检测。上传接口、插件机制、模板编辑、缓存目录权限,是 PHP Web 应用中长期高风险点。

从长期治理看,建议把以下几项作为基线:上传目录禁执行、代码目录只读、后台接口强鉴权、发布流程可追踪、日志集中留存、Web 目录文件完整性监控。做好这些基础项,很多看似复杂的入侵事件都会被限制在较小范围内。

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

0篇意见

推荐意见

没有意见。

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