Iog4j2漏洞相关技术分析
背景
2021年12月初,极光安全团队通过安全威胁舆情发现Apache Log4j2 存在远程代码执行漏洞,迅速对该漏洞危害性评估和涉及面漏斗分析,制定了应急响应方案,对涉及到的代码组件类和主机资产类快速整理,外部安全扫描对该漏洞进行优先级扫描,避免威胁进一步扩大和阻断,对内推动该漏洞版本组件的修复进度,持续关注该漏洞组件的后续发展。
本文通过构造环境复现漏洞,了解这个漏洞产生的原因,分析修复漏洞方法,研究学习背后的相关技术。
漏洞影响
Log4j2作为java代码项目中广泛使用的开源日志组件,漏洞影响范围极广,堪称史诗级、核弹级漏洞。
直接和间接依赖Log4j2的开源组件总计有17万个,也就是说有至少17万个开源组件是受Log4j2漏洞影响GitHub作为全球最大的开源代码托管平台,抽样分析发现至少5.8%的java开源项目受该漏洞影响
在java语言的开源组件流行度排行中,Log4j2列第13位
漏洞复现
利用漏洞攻击过程
复现代码
github地址https://github.com/zheng93775/log4j2-attack 按照README.md里面的步骤可以完整运行整个流程模块说明
模块说明
business-app 正常的业务应用
marshalsec 攻击方搭建,用于启动LDAP服务的开源工具
evil-http-server 攻击方搭建,启动Http服务,提供恶意代码下载
evil-http-server
首先我们准备一个含有恶意执行代码的类,恶意代码执行时会在Windows下启动计算器
使用SpringMVC的Controller,http请求返回的相应是恶意类的二进制字节码
编译后,启动服务,监听9090端口
marshalsec
marshalsec是一款开源的工具,可以用于快速启动LDAP服务github地址:https://github.com/mbechler/marshalsec
首先编译项目,然后运行jar启动LDAP服务
business-app
business-app代表正常的业务方java应用,为了方便复现,跳过正常http服务请求过程,启动后直接记录日志
符合漏洞条件的Windows系统环境下运行Main.java,就会成功打开计算器
相关技术
JNDI
什么是JNDI
JNDI 全称为 Java Naming and Directory Interface,即 Java 名称与目录服务接口。是SUN公司提供的一种标准的Java命名系统接口,在J2EE规范中是重要的规范之一。JNDI提供统一的客户端API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。
JNDI中的命名(Naming),就是将Java对象以某个名称的形式绑定(binding)到一个容器环境(Context) 中,以后调用容器环境(Context)的查找(lookup)方法就可以查找出某个名称所绑定的Java对象。
JNDI架构
JNDI 架构上主要包含两个部分,Java 的应用层接口 和 SPI。
SPI 全称为 Service Provider Interface,即服务供应接口,主要作用是为底层的具体目录服务提供统一接口, 从而实现目录服务的可插拔式安装。在 JDK 中包含了RMI、LDAP、CORBA等内置的目录服务
为什么要用JNDI
JNDI是java语言产生漏洞的一个比较大的因素,我们平时在业务开发中基本没有使用到,那么为什么log4j2的代码要支持jndi呢?
1.JNDI 提出的目的是为了解耦,是为了开发更加容易维护,容易扩展,容易部署的应用。
2.JNDI 是一个Sun提出的一个规范(类似于JDBC),具体的实现是各个厂商实现的,可以看出,老外还是非常认可这个规范,很多地方做了很多解耦的设计,包括Log4J。
3.JNDI 在J2EE系统中的角色是“交换机”,是J2EE组件在运行时间接地查找其他组件、资源或服务的通用机制。
4.JNDI 是通过资源的名字来查找的,资源的名字在整个J2EE应用中是唯一的。
除了Log4j,还有很多组件用了JNDI,比如:Hibernate、JTA、Tomcat、WebLogic、WebSphere
LDAP
全称为 Lightweight Directory Access Protocol,即轻量级目录访问协议。
LDAP是开放的Internet标准,支持跨平台的Internet协议,在业界中得到广泛认可的,并且市场上或者开源社区上的大多产品都加入了对LDAP的支持,因此对于这类系统,不需单独定制,只需要通过LDAP做简单的配置就可以与服务器做认证交互。
修复漏洞方法分析
升级log4j2
log4j漏洞受影响的版本为2.0至2.15.0.rc1,建议升级至2.17.0及以上正式版本
2.15.0.rc1
漏洞爆出的当天,2.15.0.rc1修复版本就出来了。JndiManager.lookup方法进行了修改,增加了白名单校验, 但是代码有个问题,catch住异常后代码仍然可以往下执行,攻击者在${jndi}的地址中增加一个空格就可以触发URISyntaxException,绕过白名单校验。
2.15.0.rc2
这个版本解决了rc1的问题
虽然也有爆出漏洞,但是条件比较苛刻,能利用的可能性很小,还是比较安全的。如果已经升到2.15.0,应用本身不直接对外网提供服务,也可以选择不升级到更新版本。
在某些非默认配置中,发现Apache Log4j 2.15.0中解决CVE-2021-44228的修复是不完整的。这可能允许攻击者控制线程上下文映射(MDC)输入数据,当日志记录配置使用非默认的模式布局,使用上下文查找(例
如,$${ctx:loginId})或线程上下文映射模式(%X, % MDC,或%MDC),使用JNDI查找模式生成恶意的输入数据,从而导致拒绝服务(DOS)攻击。
引文:https://github.com/cckuailong/Log4j_CVE-2021-45046
2.16.0
更新内容如下
默认禁用JNDI的访问,用户需要通过配置log4j2.enableJndi参数开启
默认允许协议限制为:java、ldap、ldaps,并将ldap协议限制为仅可访问Java原始对象Message Lookups被完全移除,加固漏洞的防御
JNDI远程执行漏洞被彻底封死了,但是还有拒绝服务漏洞
Apache官方发布了Apache Log4j 拒绝服务攻击漏洞(CVE-2021-45105),此漏洞需要在非默认配置下才能触发。当系统日志配置使用带有Context Lookups的非默认 Pattern Layout(例如$${ctx:loginId})时,攻击者可构造包含递归查找的恶意输入数据,成功利用此漏洞将触发无限循环,导致系统崩溃。
这个漏洞首要前提条件比较苛刻,已经升级到2.16.0的可以选择不升级到更新版本
升级JDK小版本
升级 jdk 版本至 6u211 / 7u201 / 8u191 / 11.0.1 以上,可以在一定程度上限制 JNDI 等漏洞利用方式。JDK高版本有什么不同呢
高版本增加了一个变量com.sun.jndi.ldap.object.trustURLCodebase,这个变量的值默认为false。除非我们加入如下代码手动复制为true,才可以在高版本复现漏洞。
JDK高版本也存在漏洞
绕过方式:LDAP服务Codebase不返回地址,而是返回一个本地已有的Factory类,通过Factory类创建指定参数的实例实现攻击。比如Tomcat中存在的org.apache.naming.factory.BeanFactory。LDAP返回示例如下
通过配置关闭log4j的jndi查找功能
有以下几种方式
设置 jvm 参数 “-Dlog4j2.formatMsgNoLookups=true
在项目 classpath 目录下添加 log4j2.component.properties 配置文件,设置 log4j2. formatMsgNoLookups=true
设置系统环境变量:“LOG4J_FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS” 设置为 “true” 注意,由于这个配置项的判断逻辑是2.10版本才加上的,这种方式对于2.0 <= log4j版本 < 2.10无效
删除log4j-core-2.x.jar中的JndiLookup类
在以前的版本(<2.16.0)中,可以通过从类路径中删除JndiLookup类来缓解这个问题。不建议使用这种方式, 某些特殊项目不便升级log4j的情况下可以考虑。
fastjson漏洞分析
2019年fastjson被爆出远程代码执行漏洞,当时国内使用了fastjson的项目非常多,影响面也非常广,并且漏洞的修复持续了N个版本。那么,fastjson具体是存在什么漏洞呢?
漏洞和fastjson中的一个AutoType特性,我们先来看看AutoType是什么。
AutoType
来看一个示例
序列化
反序列化
执行结果
为了解决这个问题,fastjson引入了AutoType
序列化后的字符串多了@type字段,反序列化时就可以定位到具体的类型了
利用漏洞攻击过程
思路是指定@type的值为某个特殊的类,比如
Java应用使用fastjson反序列化这个字符串,创建JdbcRowSetImpl类对象,这个类的dataSourceName支持传入一个rmi的源,当解析这个uri的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。