log4j 漏洞原理
以下为前置知识
JNDI
JNDI(Java Naming and Directory Interface)又称 Java 命名和目录接口,是 J2EE 中的核心规范之一,它为 Java 应用提供了一种命名和目录服务的统一接口,允许程序员查找和使用各种资源(如数据库,远程对象等),它提供了多种方式让程序员直接通过命名访问对应服务、资源甚至 Java 对象。JNDI 注入就是攻击者构造了恶意的 Java 对象供 JNDI 加载,并通过 JNDI 的 RMI 等服务执行其字节码,从而造成远程代码执行漏洞,不过现在 JNDI 注入的前提就是 JDK 版本。
- JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
- JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
- JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。
一句话:JNDI 是 Java EE 的核心规范之一,能够像映射表一样,方便开发者通过命名访问对应的资源及 Java 对象,如果 JNDI 配置不当则可能会导致安全漏洞。

RMI
RMI(Remote Method Invocation)又称为远程方法调用,它允许 JVM 对象执行另一个 JVM 对象中的方法,就像调用本地一样,尽管这些对象位于网络上的不同机器,它利用 Java 中的序列化和反序列化原理实现的,所以利用起来要非常注意安全漏洞
一句话:RMI 是一种可以让客户端 JVM 对象调用服务端上 JVM 对象的方法,服务端把结果返回给客户端的组件。
工作流程如下:
- 服务端创建远程调用对象并注册到 RMI Registry
- 客户端通过 RMI Registry 查找远程调用对象,获取存根
- 客户端通过存根调用远程方法
- 存根将调用信息序列化并发送至服务器
- 服务器反序列化调用信息,调用实际对象对应方法
- 服务器将结果序列化后返回给客户端

RMI 实现案例
- 接口代码
package share; |
- 服务端代码
package Server; |
服务端运行程序

- 客户端代码
package Client; |
客户端执行程序,弹出计算器

LDAP
LDAP(lightweight Directory and protocol)是一个基于树状方式存储的轻量目录访问协议,由于其树状存储的特点导致其有着优异的读取性能,通常用于 Web 应用和软件里的单点登录和身份认证服务,大部分应用都支持该协议,比如 Java 的 Tomcat 服务、邮件系统甚至交换机和路由器设备都支持该协议
一句话:LDAP 是一个轻量目录访问协议,通常用于单点登录和身份认证服务,常见的软硬件都支持该协议。
关于 LDAP 的一些名词如下

搭建LDAP服务器
环境:debian 11
services: |
其中域名是 demo.com,用户名是 cn=admin,dc=demo,dc=com,密码是 Qq@12345
然后 docker compose up -d
使用 LDAP
进来之后默认会有一个组织角色

我们要按次序创建组织部队、群组、Posix 用户
- 创建组织部队

- 创建群组

- 创建 Posix 用户

结果如下

Log4j 漏洞利用解析
Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。大多数情况下,开发者可能会将用户输入导致的错误信息写入日志中。
由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。
此次漏洞触发条件为只要外部用户输入的数据会被日志记录,即可造成远程代码执行。(CNVD-2021-95914、CVE-2021-44228)
Log4j2的这个漏洞本质上是JNDI注入 + LDAP的漏洞,而LDAP的利用方式在JDK 6u211、7u201、8u191、11.0.1之后,增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径给禁了。
受影响版本:Apache Log4j 2.x <= 2.15.0-rc1,2.15-rc1 存在补丁绕过
漏洞原理
漏洞原因是 log4j2 在记录日志时,会对 ${} 的内容进行递归解析,并通过 JNDI 从远程服务器加载对象,关键在于 log4j2 内部有一套 lookup机制,里面配置了 JNDI、env、Java 等解析器,而 JNDI 又可以通过 LDAP、RMI 等协议从远程服务器加载字节码并执行,从而触发了该漏洞,由于 JNDI 是通过序列化和反序列化的方式远程执行远程服务器中的字节码的,使得该漏洞容易被利用且能轻易绕过部分 waf
该漏洞的本质是 JNDI + LDAP 注入
整体攻击流程如下:
1.用户输入恶意字符串 -> |
如何修复
- 临时修复方法
- 设置启动参数以禁用 log4j2 中的 lookup 查找 -Dlog4j2.formatMsgNoLookups=true
- 在代码层对用户的输入进行过滤
- 永久修复
- 升级 Java 版本
总结
Log4j2 漏洞的根本原因在于:
- 过度设计:日志框架不应该支持如此强大的动态解析功能
- 递归解析:对 ${} 进行无限递归解析,没有深度限制
- JNDI 滥用:结合了 JNDI 的动态类加载能力
- 默认开启:危险功能默认开启,没有安全警告