log4j 漏洞一些特殊的利用方式

背景

正愁本周没主题可写,结果两天前就曝了一个核弹级的漏洞“log4j RCE”,这两天官方的修补方案也逐渐完善。所以本篇就拿 log4j 作为主题讲一下我的几个发现。

RCE

log4j RCE 原理已经有挺多人发过了,本文不过多赘述。简单说就是日志在打印时遇到 ${ 后 Interpolator 类按照 : 分割出第一部分作为 prefix 第二部分作为 key。通过 prefix 去找对应的 lookup,再通过对应的 lookup 实例调用 lookup 方法传入 key 作为参数。

log4j-core 自带的 lookup 有很多实例,其中就包括了此次存在漏洞的 JndiLookup 实例。JndiLookup 则是直接把传进来的 key 当做 JNDI URL 用 InitialContext.lookup 去访问,从而造成了 JNDI 代码执行漏洞。

WAF bypass

该漏洞曝光后各安全厂商也纷纷推出了解决方案,WAF、RASP、改源码、改配置文件、更新到rc2等。

在此次漏洞中最没有防御效果的就是 WAF 了。有提出 ${jndildaprmi 等关键词规则的防护。但研究后发现都会存在被绕过问题。

首先是 jndi、ldap 简直太容易被绕过,只要用 lowerCase upperCase 就可以把关键词分割开。

如果是用了正则的话那还可以使用 upper 把 jndı 转成 jndi。

注意:这里的 ı 不是 i和I,经过 toUpperCase 就会转变成 I。从而绕过了 jndi 关键词的拦截。

再就是 ${ 关键词的拦截了,虽然这个范围有点大可能会产生一些误报,但鉴于漏洞的严重性宁可错杀不可放过还是有人会这么防的。

但这样也未必能够真正的解决,因为漏洞的触发点是在打印日志的时候把可控内容携带进去了。那么可控内容从哪里来?

Header、URL、键值对参数、JSON参数、XML参数 ...

现在随着 JSON 数据格式的流行,很多系统都在使用 JSON 处理参数,JSON 处理库用的最多的就数 Jackson和fastjson。

而 Jackson 和 fastjson 又有 unicode 和 hex 的编码特性。

例如:

{"key":"\u0024\u007b"}
{"key":"\x24\u007b"}

这样就避开了数据包中有 ${ 的条件,所以 WAF 的防护规则还要多考虑几种编码。

信息泄露

sys、env 这两个 lookup 的 payload 也在讨论中被频繁提起,实际上他们分别对应的是 System.getProperty()System.getenv(),能够获取一些环境变量和系统属性。部分内容是可以被携带在 dnslog 传出去的。

除了 sys、env 以外我还发现 ResourceBundleLookup 也可以获取敏感信息,但没有看到有人讨论 Bundle。

public String lookup(final LogEvent event, final String key) {
    if (key == null) {
        return null;
    }
    final String[] keys = key.split(":");
    final int keyLen = keys.length;
    if (keyLen != 2) {
        LOGGER.warn(LOOKUP, "Bad ResourceBundle key format [{}]. Expected format is BundleName:KeyName.", key);
        return null;
    }
    final String bundleName = keys[0];
    final String bundleKey = keys[1];
    try {
        // The ResourceBundle class caches bundles, no need to cache here.
        return ResourceBundle.getBundle(bundleName).getString(bundleKey);
    } catch (final MissingResourceException e) {
        LOGGER.warn(LOOKUP, "Error looking up ResourceBundle [{}].", bundleName, e);
        return null;
    }
}

从代码上来看就很好理解,把 key 按照 : 分割成两份,第一个是 bundleName 获取 ResourceBundle,第二个是 bundleKey 获取 Properties Value

ResourceBundle 在 Java 应用开发中经常被用来做国际化,网站通常会给一段表述的内容翻译成多种语言,比如中文简体、中文繁体、英文。

那开发者可能就会使用 ResourceBundle 来分别加载 classpath 下的 zh_CN.properties、en_US.properties。并按照唯一的 key 取出对应的那段文字。
例如:
zh_CN.properties

LOGIN_SUCCESS=登录成功

ResourceBundle.getBundle("zh_CN").getString("LOGIN_SUCCESS") 获取到的就是 登录成功

如果系统是 springboot 的话,它会有一个 application.properties 配置文件。里面存放着这个系统的各项配置,其中有可能就包含 redis、mysql 的配置项。当然也不止 springboot,很多其他类型的系统也会写一些类似 jdbc.properties 的文件来存放配置。

这些 properties 文件都可以通过 ResourceBundle 来获取到里面的配置项。所以在 log4j 中 Bundle 是比sys和env更严重的存在。

1639213843363.jpg

在不出网的环境下可以通过 dnslog 的方式来外带信息。

除了dnslog以外还可以通过这两种方法来获取信息。

ldap
1639216933333.jpg

dns
1639215324785.jpg

修复方案

log4j 更新到最新版本

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。