一次 Nginx Lua 木马应急响应实录:从移动端跳转博彩站到 root 级入侵溯源

联系方式 & 交流群
- 微信: gzs-47
进微信群请联系博主,各位觉得文章对你有帮助的话可否打赏一些呀~
事件等级:🔴 严重 — 已确认 root 级入侵
攻击链:root SSH 认证登录 → Nginx Lua 木马植入 → 服务脚本持久化 → 日志清洗
持久化方式:init.d 服务脚本回灌 + chattr +i 文件锁定 + 时间戳伪造
处置结果:活跃链路已切断,已知残留已隔离,证据已归档
一、事件概述
某电商站点运维人员收到用户反馈:移动端访问站点时偶发跳转到博彩风格的外部页面,桌面端访问正常。同时该站点通过 CDN 访问时频繁出现 520 错误。
初步排查后发现,这不是前端 Bug,也不是支付插件异常,而是一次已经落证的服务器侧入侵事件。攻击者通过已认证的 root SSH 会话进入系统,在 Nginx 层植入 Lua 恶意模块实现流量劫持,并配合了服务脚本持久化、文件不可变属性锁定和日志清洗等全套反取证手段。
本文脱敏后完整记录此次应急响应的全流程,包括排查思路、取证方法、处置步骤和经验教训。
二、事件影响
- 业务层面:移动端用户访问站点时被服务端重定向到外部博彩风格域名,直接影响用户体验和业务信誉。
- CDN 层面:CDN 出现间歇性
520错误,并非单纯的 CDN 问题,而是叠加了 Nginx worker 进程崩溃风险。 - 支付链路:排查期间发现支付反代服务偶发超时,经分析属于 Docker 网络桥接配置问题,与 Lua 木马无直接关联。
- 运维安全:root 密码登录仍处于开放状态,且系统日志被清洗,导致部分入侵入口无法百分百回溯。
三、核心时间线
3.1 首次入侵(约一个月前)
HIDS 记录到一条真实的 root SSH 会话,该会话执行了以下操作:
| 动作 | 说明 |
|---|---|
| 修改 Nginx 配置文件 | 操作 proxy.conf 等反向代理配置 |
| 植入恶意 Lua 文件 | 将恶意 WAF 脚本写入 Nginx 相关目录 |
| 锁定文件 | 对植入文件执行 chattr +i 使其不可变 |
| 伪造时间戳 | 修改文件修改时间,掩盖植入时间点 |
| 重启 Nginx | 执行 nginx -t && /etc/init.d/nginx restart 使木马生效 |
| 清洗日志 | 清空 /var/log/auth.log、wtmp、btmp、审计日志 |
| 清除痕迹 | 执行 history -c 和 lastlog 清除命令历史和登录记录 |
关键判断:这不是端口扫描或爆破尝试,而是「已认证」的入侵行为——攻击者拥有合法的 root 凭据。
3.2 第二次落地(事发当天凌晨)
系统出现第二段落地行为:
- 服务启动脚本
/etc/init.d/nginx被植入恶意回灌逻辑 - 出现可疑的
.so动态库文件 - 出现隐藏标记文件,内容为木马版本标识
- 内核日志出现 Nginx worker 进程的段错误
关键判断:这不是临时文件误报,而是服务重启级的持久化链路——即使当前恶意文件被删除,重启 Nginx 后木马会自动复活。
3.3 应急响应窗口
从用户反馈到完整处置,应急响应推进路径如下:
- 复现移动端 UA 与桌面端访问表现差异
- 对比应用层日志和 Nginx 访问日志
- 确认异常
302重定向出现在 Nginx 层而非 PHP 应用层 - 通过
nginx -T导出全量配置,顺 Lua 加载路径定位恶意模块 - 先备份证据,再替换为 no-op 空壳
- 深挖持久化点,确认 init.d 脚本回灌逻辑
- 清理认证面、隔离残留样本、打包证据并记录哈希
- 复测移动端、桌面端、支付接口和关键服务状态
四、排查分析过程
4.1 区分问题层级
用户报障后,首先要判断异常发生在哪一层:
| 层级 | 如果是这层的问题 | 实际表现 |
|---|---|---|
| 前端 / CDN 缓存 | 服务端日志仍返回正常 HTML | ❌ 服务端日志存在异常 302 |
| PHP 应用层 | 应用代码、路由或日志中能找到跳转逻辑 | ❌ 应用层无异常 |
| Nginx 请求入口层 | 访问日志出现应用无感知的 302 | ✅ 命中了 |
本次通过移动端 UA 请求复现了 302,并发现跳转参数中包含原始域名和设备类型,说明跳转逻辑在 HTTP 请求入口层——也就是 Nginx 层被劫持。
4.2 访问日志分析
访问日志中出现了关键特征:
正常路径如 /、/item/1、/products → 间歇性 302,与正常 200 混杂这个特征非常关键:
- 正常业务跳转通常有稳定规则(如未登录跳登录页)
- 恶意流量劫持常按设备类型、流量比例、来源 Referer 或时间随机触发
- 本次恶意 Lua 中确实存在移动端比例控制逻辑——只在部分移动端请求中触发跳转
4.3 定位 Nginx Lua 加载链
审计 nginx -T 全量配置后,重点排查以下位置:
lua_package_path— Lua 模块搜索路径rewrite_by_lua/access_by_lua— 请求阶段 Lua 钩子include luawaf.conf— WAF 配置文件- Nginx Lua 库目录
- WAF 脚本目录
- 反向代理配置文件
最终确认活跃恶意模块是一个伪装成正常模块名的 .lua 文件,内容包含:
- 外部博彩风格跳转域名
- 移动端 UA 识别和比例触发逻辑
- 远程配置拉取功能
ngx.redirect()跳转实现
与线上表现完全一致。
4.4 先取证再处置
本次没有直接删除任何可疑文件。 原因:
- 直接删除会破坏法证链,无法回溯入侵过程
- 直接删除 Lua 文件可能导致仍存在的调用点把 Nginx 打挂
- 攻击者使用了
chattr +i、时间戳伪造和重启回灌,单点删除不能解决持久化
处置策略:
- 将恶意原件保留到证据备份目录
- 生产路径中的恶意 Lua 替换为同名 no-op 空壳模块
- 保留导出函数签名,避免隐藏调用点异常
- 执行
nginx -t验证配置后再 reload
4.5 追踪持久化机制
仅替换当前活跃恶意模块后,不能认为修复完成。后续检查发现 init.d 服务脚本已被改造:
- 从外部 URL 拉取远程载荷
- 向 Nginx 配置文件写入 Lua 钩子
- 操作
lua_package_path确保模块加载 - 操作
ld.so.preload预加载共享库 - 配合
chattr +i保护植入文件
这就是木马的复活机制——只要 Nginx 服务重启,远程载荷会重新落地,配置会被重新注入。清理当前文件而不处理服务脚本,等于什么都没修。
4.6 用 HIDS 建立根因证据链
系统日志和 shell history 被清空后,常规取证手段失效。本次的关键证据来自 HIDS(主机入侵检测系统)的日志:
- HIDS 中保留了 root SSH 会话记录及命令样本
- 能证明约一个月前已发生真实入侵
- 这个证据比猜测 IP 来源、猜测插件漏洞有价值得多
教训:HIDS 日志是日志清洗后唯一可信的证据源,必须保证不被同机单点覆盖,最好外送或异地备份。
4.7 扩展扫描残留
切断已知攻击链后,继续检查:
| 检查项 | 内容 |
|---|---|
| 进程 | 当前进程列表、高 CPU 进程 |
| 端口 | 监听端口和对应进程 |
| 服务 | systemd 启用服务和 timer |
| 定时任务 | root 与系统级 crontab |
| 预加载 | /etc/ld.so.preload |
| 启动脚本 | shell profile、自启动脚本 |
| 认证文件 | authorized_keys、uid=0 账户 |
| SUID | SUID 提权文件 |
| Nginx worker | 当前加载的动态库映射 |
| 近期变更 | 最近修改的系统文件 |
| IOC 搜索 | 恶意模块名、可疑域名、隐藏标记文件 |
最终确认并隔离了三个残留工件:隐藏标记文件、恶意动态库、异常数据文件。
4.8 认证面收敛(避免自锁)
发现 root 已泄露后,凭据轮换必须做,但不能为了「看起来更安全」直接关闭当前唯一的远程入口。
本次策略:
- 清空 root 的 authorized_keys
- 隔离未确认但仍需使用的 SSH 密钥
- 确认没有其他 uid=0 账户或异常用户的认证文件
- 暂时保留 root 密码登录,等待新管理员入口就绪后再关闭
这是安全和可运维之间的必要取舍——没有替代入口时直接关闭 root 登录,只会把正常运维锁在外面。
4.9 四层验证
| 验证层次 | 检查内容 | 结论 |
|---|---|---|
| 配置层 | nginx -t、nginx -T 全量导出 | ✅ 无语法错误,无恶意 include |
| 运行层 | 进程、端口、worker 映射库、内核日志 | ✅ 无崩溃,无异常动态库加载 |
| 业务层 | 移动端 UA / 桌面端 UA 访问核心页面 | ✅ 200 正常,无跳转 |
| 支付层 | 支付配置接口、支付回调日志 | ✅ 200 正常 |
只看服务状态 active 不等于业务可用;只看页面返回 200 也不等于木马已清理。必须四层同时验证。
五、处置措施总结
已执行操作
- 将恶意 Lua 模块替换为 no-op 安全空壳
- 将恶意 WAF 脚本替换为安全空壳
- 移除 init.d 服务脚本中的恶意回灌逻辑
- 移除 Nginx 全局配置中的异常
lua_package_path - 去除攻击者遗留在相关文件上的
immutable属性 - 隔离隐藏标记文件、恶意动态库、异常数据文件
- 清理 root 本机 SSH 私钥驻留
- 归档完整事件证据并生成 SHA256 校验值
- 恢复 Docker bridge 网关,修复支付反代超时问题
后续动作
24 小时内:
- 持续观察 Nginx 访问日志中的异常状态码
- 持续观察内核日志中的 worker 崩溃
- 观察支付链路的稳定性
- 保存 CDN 安全事件和访问日志
1-3 天内:
- 新建非 root 管理员用户并配置公钥
- 验证新入口可用后,关闭 root 密码直接登录
- 轮换面板密码、API key、部署密钥和数据库密码
- 将关键日志外送或定时同步到异地
中期:
- 评估系统重装和业务迁移窗口
- 用干净系统重新部署服务栈
- 只迁移业务数据,不迁移旧系统脚本和二进制
- 建立上线后基线:端口清单、文件哈希、日志保留策略
六、经验教训
1. 偶发跳转必须优先怀疑请求入口层
移动端跳转博彩站、桌面端偶发正常——这是典型的按 UA 或比例触发的流量劫持特征。不能只从前端缓存或应用路由找原因,必须第一时间查 Nginx 层的异常。
2. CDN 520 可能是源站进程崩溃
CDN 的 520 错误表示源站连接异常。本次 520 与恶意 .so 文件导致的 Nginx worker 进程崩溃直接相关。排查 CDN 错误时必须同时看源站内核日志和 worker 状态。
3. 单点删除木马不等于修复
攻击者使用了:
- 服务脚本持久化(重启复活)
chattr +i文件不可变属性(防删除)- 时间戳伪造(掩盖时间线)
- 日志清洗(销毁证据)
只替换一个恶意文件会留下重启复活的风险,必须深挖持久化链。
4. 证据保存必须早于清理
先打包证据、记录哈希、保留备份,再清理生产路径。否则后续无法解释完整的入侵链,也无法判断是否存在第二阶段攻击。
5. 安全加固不能盲目锁死
关闭 root 密码登录是正确方向,但前提是有可验证的新管理员入口。没有替代入口就直接关闭,等于把自己也锁在门外。
6. HIDS 是日志清洗后唯一的可信证据源
系统日志被清空后,HIDS 日志成为了唯一能证明入侵时间点和攻击行为的关键证据。后续必须保证 HIDS 日志异地存储,不能被同机攻击者一并清理。
7. 运行态问题要和入侵事件分层处理
本次排查中发现的支付反代超时问题,经分析是 Docker 网络配置问题,与 Lua 木马无关。应急响应中必须严格区分:
- 安全事件(入侵、木马、持久化)
- 运行态故障(网络、配置漂移、资源耗尽)
- 业务 Bug(代码逻辑、支付流程)
混为一谈会误导排查方向,浪费时间。
七、应急响应检查清单
以下模板可直接复用于类似事件的排查:
1. 复现用户侧异常 记录 UA、URL、时间、响应码、跳转目标
2. 分层对比日志 CDN → Nginx → 应用 → 系统日志,定位异常发生层级
3. 审计 Web 服务器配置 导出全量配置,检查 Lua/WAF/include/proxy/rewrite/动态模块
4. 搜索 IOC 关键词搜索,但不要在取证前删除任何文件
5. 取证归档 对可疑文件做 stat、lsattr、sha256sum、备份归档
6. 检查持久化点 systemd、init.d、cron、profile、ld.so.preload、Docker entrypoint
7. 检查认证面 uid=0 账户、authorized_keys、私钥、面板用户、API key
8. 检查运行态 进程、端口、worker 映射库、内核崩溃日志
9. 最小化清理 先切断活跃链路,再处理持久化机制和残留文件
10. 重新验证 配置层 → 运行层 → 业务层 → 支付层,四层全部验证
11. 归档与复盘 归档证据和哈希,记录回滚点与剩余风险八、判断边界
可以确认的:
- 存在已认证的 root SSH 入侵行为
- 存在 Nginx 服务脚本级持久化机制
- 移动端博彩跳转来自服务端 Nginx Lua 恶意模块
- 恶意动态库与 Nginx worker 进程崩溃相关
- 已知活跃链路已切断,已知残留已隔离
不能百分百确认的:
- 最初 root 凭据是如何泄露的
- 是否还存在未知 rootkit 或内核级隐藏组件
- 不同时间点的两次操作是否完全同源
- 不重装系统的情况下机器是否永远可信
因此后续安全策略不应停留在「已清理木马」,而应按「已发生 root 级入侵后的恢复流程」推进——最终闭环仍是重装系统、重建凭据、重新部署业务。
本文由真实应急响应案例脱敏整理,涉及域名、IP、路径等信息已做匿名化处理。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














