安全研究言零的博客
一次 Nginx Lua 木马应急响应实录:从移动端跳转博彩站到 root 级入侵溯源
联系方式 & 交流群 QQ : 46333839 微信 : GOV-HACK 进微信群请联系博主,各位觉得文章对你有帮助的话可否打赏一些呀~ 事件等级:严重 — 已确认 root 级入侵 攻击链:root SSH 认证登录 → Nginx Lua 木马植入 → 服务脚本持久化 → 日志清洗 持久化方式:init.d 服务脚本回灌 + chattr +i 文件锁定 + 时间戳伪造 处置结果:活跃链路已切断,已知残留已隔离,证据已归档 一、事件概述 某电商站点运维人员收到用户反馈:移动端访问站点时偶发跳转到博彩风格的…
内容摘要
联系方式 & 交流群
QQ : 46333839
微信 : GOV-HACK
进微信群请联系博主,各位觉得文章对你有帮助的话可否打赏一些呀~
事件等级:严重 — 已确认 root 级入侵
攻击链:root SSH 认证登录 → Nginx Lua 木马植入 → 服务脚本持久化 → 日志清洗
持久化方式:init.d 服务脚本回灌 + chattr +i 文件锁定 + 时间戳伪造
处置结果:活跃链路已切断,已知残留已隔离,证据已归档
一、事件概述
某电商站点运维人员收到用户反馈:移动端访问站点时偶发跳转到博彩风格的外部页面,桌面端访问正常。同时该站点通过 CDN 访问时频繁出现 520 错误。
初步排查后确认,这是一次已经落证的服务器侧入侵事件,前端和应用层均无异常。攻击者通过已认证的 root SSH 会话进入系统,在 Nginx 层植入 Lua 恶意模块实现流量劫持,并配合了服务脚本持久化、文件不可变属性锁定和日志清洗等全套反取证手段。
本文脱敏后完整记录此次应急响应的全流程,包括排查思路、取证方法、处置步骤和经验教训。
二、事件影响
业务层面:移动端用户访问站点时被服务端重定向到外部博彩风格域名,直接影响用户体验和业务信誉。
CDN 层面:CDN 出现间歇性 520 错误,实际叠加了 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 清除命令历史和登录记录
从 HIDS 记录判断,这属于「已认证」的入侵行为,攻击者拥有合法的 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、路径等信息已做匿名化处理。
资讯来源
言零的博客