跳转到帖子

背景

最近整理了一次比较典型的 WebShell 告警处置过程。场景并不复杂:某业务系统的运行目录中出现异常脚本文件,安全设备给出了疑似 WebShell 告警。后续排查发现,入口点并不是常见的弱口令后台,而是一个文件上传接口在扩展名校验、存储路径和访问控制上都存在缺陷。

这类问题在企业内网系统里很常见:业务代码年代较久,上传功能为了兼容各种文件类型逐步放宽限制,最后变成“能上传、能访问、能解析”的危险组合。本文不讨论攻击利用细节,重点放在应急排查、代码审计和修复加固上。

问题场景

告警信息中能看到几个关键点:

  • 异常文件出现在 Web 根目录下的 upload 目录;
  • 文件后缀看起来像图片,但内容中存在脚本特征;
  • 访问日志中存在对该文件的直接 HTTP 请求;
  • 同一来源 IP 在短时间内多次访问上传接口和异常文件。

从防守角度看,这已经足够判断需要进入应急流程:先止血,再保全证据,最后定位根因并修复。

第一步:现场保护与初步确认

很多人在处理 WebShell 告警时会第一时间删除文件,这个习惯不太好。更稳妥的做法是先隔离访问能力,再保留样本和日志。比如临时在 Web 服务器层面禁止访问上传目录中的可执行脚本,或者将对应目录挂载为不可执行。

Nginx 环境中可以先加一条临时规则,阻断上传目录内脚本解析:

location ~* ^/upload/.*\.(php|jsp|jspx|asp|aspx)$ {
    return 403;
}

如果是 PHP-FPM,还需要确认 location 匹配顺序,避免被其他规则覆盖。对于 Java 应用,重点看上传目录是否被映射到可直接访问的静态资源路径,是否存在 JSP 被容器解析的可能。

保留证据时,至少建议记录:

  • 异常文件路径、文件大小、修改时间、权限;
  • 文件 hash;
  • 相关访问日志、错误日志、应用日志;
  • 上传接口请求时间附近的操作日志;
  • 服务器进程、网络连接、计划任务和最近新增文件。
sha256sum /path/to/upload/suspicious_file
stat /path/to/upload/suspicious_file
find /var/www -type f -mtime -3 -ls

日志分析:把时间线串起来

应急排查里最重要的是时间线。单独看到一个 WebShell 文件,只能说明“存在异常文件”;只有结合日志,才能判断入口、影响范围和是否发生后续动作。

可以从访问日志里筛选上传接口和异常文件访问记录:

grep -E "POST /.*upload|/upload/" access.log | awk '{print $1,$4,$5,$6,$7,$9,$10}'

需要关注以下字段:

  • 请求方法:是否存在异常 POST;
  • 状态码:上传是否成功,异常文件是否被访问成功;
  • User-Agent:是否明显为脚本化工具;
  • Referer:是否来自正常业务页面;
  • 源 IP:是否与正常用户行为一致;
  • 请求时间:是否与文件 mtime 对得上。

如果应用层有审计日志,还要看上传人、业务单号、文件原名、存储名、MIME 类型、文件大小等字段。很多时候,Web 访问日志只能看到结果,应用日志才能定位到具体接口和参数。

样本分析:判断风险而不是炫技

对异常文件的分析,建议遵循“静态优先、隔离环境验证”的原则,不要在生产机上执行。静态分析通常已经足够判断风险,例如:

  • 脚本中是否存在命令执行、文件读写、反射调用等高危能力;
  • 是否对参数进行加密、编码或混淆;
  • 是否具备连接远程地址、下载文件、横向移动的迹象;
  • 是否存在固定密钥、特征字符串或行为标识。

常用的基础检查命令如下:

file suspicious_file
strings -a suspicious_file | head -n 50
grep -nE "eval|assert|system|exec|passthru|shell_exec|base64_decode|Runtime|getRuntime|ProcessBuilder" suspicious_file

如果文件伪装成图片,可以检查文件头和真实内容是否一致:

xxd -l 64 suspicious_file
file suspicious_file

这里的目标不是复现控制过程,而是回答几个防守问题:它是否可执行?是否被访问过?是否有持久化或进一步投递行为?是否可能造成数据泄露?

根因定位:上传功能常见审计点

本次问题的根因在上传接口。代码审计时可以从以下几个点入手。

1. 扩展名校验是否可靠

只依赖前端校验或者简单字符串判断都不可靠。常见错误包括:

  • 只检查文件名中是否包含 .jpg、.png;
  • 使用黑名单过滤危险后缀;
  • 未处理大小写、双后缀、特殊字符;
  • 上传后仍保留用户可控文件名。

更合理的方式是使用白名单,并且服务端重新生成文件名。示例思路如下:

String ext = getSafeExtension(originalFilename).toLowerCase(Locale.ROOT);
Set<String> allow = Set.of("jpg", "jpeg", "png", "pdf", "docx");
if (!allow.contains(ext)) {
    throw new IllegalArgumentException("unsupported file type");
}
String saveName = UUID.randomUUID().toString().replace("-", "") + "." + ext;

注意:白名单不是只看扩展名,还应结合业务场景校验文件内容类型。

2. MIME 类型和文件内容是否校验

Content-Type 来自客户端,不能信任。更稳妥的做法是读取文件头或使用成熟库判断文件类型。例如图片类上传,可以用图片解码库尝试解析,解析失败直接拒绝。

BufferedImage image = ImageIO.read(uploadedFile.getInputStream());
if (image == null) {
    throw new IllegalArgumentException("invalid image file");
}

对于 PDF、Office 等文件,也建议结合魔数、文件结构解析和杀毒/沙箱扫描结果综合判断。不要把“Content-Type=image/png”当作安全依据。

3. 上传目录是否可执行

这是很多系统被打穿的关键点。即使上传校验有疏漏,只要上传目录不具备脚本执行能力,风险会明显下降。

建议:

  • 上传文件放到 Web 根目录之外;
  • 通过受控下载接口读取文件,而不是直接暴露真实路径;
  • 静态资源服务器禁止脚本解析;
  • 对象存储设置合理的 Content-Type 和下载策略;
  • 文件名、路径、桶名不要由用户直接控制。

Nginx 可参考如下限制:

location /upload/ {
    autoindex off;
    add_header X-Content-Type-Options nosniff;
}

location ~* ^/upload/.*\.(php|phtml|jsp|jspx|asp|aspx|sh|pl|py)$ {
    return 403;
}

4. 下载接口是否存在路径穿越

有些系统虽然把文件放在 Web 根目录外,但下载接口写得不严谨,仍然可能被读取任意文件。审计时重点看是否直接拼接用户传入的 path、filename。

不推荐的模式:

// 风险示例:用户参数直接拼接路径
File file = new File(baseDir + "/" + request.getParameter("file"));

更稳妥的方式是只允许通过文件 ID 下载,由服务端查询真实路径,并校验路径规范化结果必须位于允许目录下:

Path base = Paths.get("/data/upload").toRealPath();
Path target = base.resolve(serverSideFileName).normalize();
if (!target.startsWith(base)) {
    throw new SecurityException("invalid file path");
}

影响面排查

确认入口后,还需要判断是否有后续影响。建议检查以下内容:

  • Web 目录近几天新增或修改的脚本文件;
  • 系统临时目录中的异常文件;
  • crontab、systemd、启动项是否有新增任务;
  • 应用账号权限是否过大;
  • 数据库账号是否存在异常登录或敏感查询;
  • 是否有异常外联、DNS 请求或下载行为。
find /var/www -type f \( -name "*.php" -o -name "*.jsp" -o -name "*.jspx" \) -mtime -7 -ls
crontab -l
systemctl list-timers
ss -antp

如果服务器上部署了 EDR 或主机审计,可以结合进程树、文件落地、网络连接做交叉验证。单看 Web 日志容易漏掉后续动作。

修复与加固建议

修复不要只停留在删除 WebShell。建议按优先级处理:

  • 立即禁用上传目录脚本执行能力;
  • 修复上传接口,改为白名单、重命名、内容校验;
  • 上传文件迁移到 Web 根目录外或对象存储;
  • 补充上传、下载、预览接口的鉴权;
  • 清理异常文件和可疑任务;
  • 轮换可能泄露的密钥、数据库密码、后台账号密码;
  • 补充日志字段,至少记录上传人、文件 ID、hash、大小、IP;
  • 建立定期巡检规则,对 Web 目录新增脚本进行告警。

如果业务允许,上传文件最好统一走异步扫描流程:文件先进入临时区,扫描通过后再入库或进入正式存储。这样即使前端或接口出现疏漏,也能增加一道缓冲。

常见误区

  • 只删文件不查入口:很容易反复出现;
  • 只看扩展名不看存储路径:上传目录可执行才是高风险组合;
  • 只查 Web 日志不查主机行为:可能漏掉后续落地文件;
  • 黑名单过滤危险后缀:维护成本高,遗漏概率大;
  • 把内网系统当低风险系统:内网系统往往权限更高,日志更弱。

总结

WebShell 告警处置的核心不是“看到异常文件就删除”,而是把入口、样本、访问记录和主机行为串成完整链路。上传功能的安全设计也不应只依赖某一个校验点,而要做到:类型白名单、服务端重命名、内容校验、不可执行存储、受控下载、完整审计。

从长期看,最值得投入的是两件事:一是把上传和下载能力收敛成统一组件,减少各业务重复造轮子;二是建立 Web 目录变更监控和日志关联规则。这样即使问题再次出现,也能更早发现、更快定位,避免从一个小上传点演变成完整入侵事件。

服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意

0篇意见

推荐意见

没有意见。

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