1 Ognl漏洞简单回顾
's2-032'、's2-033' 和 's2-057' 都是因为将恶意 的Ognl 的表达式直接存入了 'ActionMapping' 实例中,从而造成了 'Ognl' 命令注入。下面将介绍这三个漏洞中未经处理的参数:
1.1 s2-032
第139行代码为漏洞的起因,未对传入 'method' 变量进行过滤而直接存入了 'ActionMapping' 实例中。虽然在 'struts2' 整个参数的传递过程中使用了 'escapeHtml4' 会对参数进行处理,但是这种处理也是存在绕过方式,具体绕过就不在这里过多分析。
1.2 s2-033
's2-033' 是将 'actionMethod' 变量的值直接存进了 'ActionMapping' 实例中。
1.3 s2-037
037未经过处理的参数如上图所示。
恶意的 'Ognl' 表达式存入了 'ActionMapping' 实例中,在后续的执行流程中,'ActionProxy'实例取出'ActionMapping'中的值('getName()/getNameSpace()/getActionMethod()'),传给 'OgnlUtil' 实例执行 'Ognl' 表达式 ('compileAndExecute()') 。
2 Ognl漏洞挖掘通用模式
通过上文的分析,挖掘 'Ognl' 的相关漏洞可以分为两个步骤:
1. 找到一条路径,使得恶意构造的数据能够被当作 'Ognl' 表达式被解析;
2. 绕过 'Ognl' 沙箱(本文不会涉及 'Ognl' 的绕过,想要了解的可以查看参考链接)。
而对于步骤1,有两种方法可以快速定位到可能的漏洞点:
A:根据功能特性进行分析,比如 's2-032' 所涉及到的功能点为动态方法调用, 's2-033' 和 's2-037' 涉及到的功能点为 'REST' 插件,这两个功能都能直接使用'Ognl'表达式。
B:根据数据流进行分析,本方法是上一种方法的抽象表示,如果存在某个功能使用了 Ognl 表达式,那么必然会调用获取 Ognl 表达式的函数和执行 Ognl 表达式的函数。
第二种定位漏洞的方式刚好就是 codeql 数据流分析的使用场景,获取 Ognl 表达式的函数为 Source 点,而执行 Ognl 表达式的函数为 Sink 点。
3 codeql实战
'codeql' 是基于静态分析的,这也就是说 'codeql' 只从语义上分析了数据流的可达性,但至于是否可达并没有保证。同时,由于 'Java' 语言的某些运行时特性,也使得 'codeql' 在查找数据流的时候会存在遗漏的情况。笔者将解决这两类问题的办法称之为辅助截断数据流向和辅助建立数据流向,分别对应着 'DataFlow::Configuration' 类的 'isBarrier' 和 'isAdditionalFlowStep'。
3.1 建立数据流向
在建立数据流向的时候,包括但不仅限于如下的几类情况:
1. 使用继承 'DataFlow::Configuration' 的类来做污点追踪的时候,使用的是全局数据流分析,全局数据流分析指的是函数和函数之间的数据流分析。但并不是所有的代码都只存在全局数据流分析,可能还涉及到本地数据流,所以此时需要额外的建立本地数据流分析。
2. 在 'codeql' 进行污点标记的时候,只是标记了一定会被污染的情况,但有一些污点会因为函数调用的不确定性而具有不同的状态,例如下面的情况:
public void foo(String taint) {
this.field = taint;
}
public void bar() {
String x = this.field;
}
在这种情况下,如果 'foo()' 在 'bar()' 之前被调用,那么变量 'x' 就被污染了,反之则没有被污染。
除了以上两种情况外,还存在一些需要辅助建立数据流向的情况:
1. 'Java' 的需要运行时才能知道的数据流向,例如 'Java' 中的消息传递,包括但不限于 'wait/notify' 和 'rpc',对于这类情况需要具体的情况具体分析。
2. 在异常处理的时候,有些的Sink点只有在抛出异常的时候才会触发。
3.2 截断数据流向
强制建立数据流向的过程具有一定通用性的,但是对于截断数据流向的解决办法,则需要根据不同额代码做具体分析。接下来,以通过 'codeql' 查找出来的 'ScopeInterceptor.java'->'OgnlUtil.java' 这条路径为例进行分析,路径详情如下所示:
数据流向为:'ScopeInterceptor.java'-->'ValueStackShadowMap.java'-->'OgnlValueStack.java' --> 'OgnlUtil.java'。虽然 'codeql' 查找到了这条能够从 'Source' 到 'Sink' 的路径,但如本章开头提到的, 'codeql' 只是从语义上分析了代码,并不保证其可达。所以,在每个跨类调用的地方都需要人为的进行分析。笔者将调用关系简单画成了下图:
注:这个图表明在 'ScopeInterceptor' 类中的 'before' 方法会调用 'ValueStackShadowMap' 类中的方法;'ValueStackShadowMap' 类中的 'findValue' 方法会调用 'OgnlValueStack' 类中的方法;'OgnlValueStack' 类中的 'getValue' 方法会调用 'OgnlUtil' 类中的方法。
下面将分别分析 'before()'、'findValue()' 和 'getValue()' ,来判断这个路径是否可达。
1. 'before()'
上图红框中的代码为 'codeql' 提示的调用 'ValueStackShadowMap' 类中方法的代码。因为 'ValueStackShadowMap()' 继承于 'Map' 接口,所以这里 'Map' 类型的变量 'app' 调用 'get' 方法时 'codeql' 认为可能会调用到 'ValueStackShadowMap' 对象。但在实际的 'Struts2' 框架中,'ValueStackShadowMap'类是'jasperreports'插件中使用的数据类型,不存在在此调用的可能性。综上分析,这条数据流是不存在的,所以 'isBarrier' 如下所示:
override predicate isBarrier(DataFlow::Node node) {
exists(Method m | (m.hasName("get") or m.hasName("containsKey")) and
m.getDeclaringType().hasName("ValueStackShadowMap") and
node.getEnclosingCallable() = m
)
}
剩下的分析和 before 函数分析一致,这里就不再叙述了。
4 总结
早在2018年,Github 安全实验室就发布了如何通过 codeql 对已有的漏洞做 variant analysis 的文章。在今年的 Blackhat 上,360 Alpha Lab 也介绍了他们是如何将 codeql 利用到 chrome 漏洞挖掘上。虽然 codeql 是个功能十分强大的工具,但经过一段时间的使用,笔者认为如果使用者对漏洞原理和目标都有着比较深入的理解,那么'codeql' 将会是一款十分好用的神器。但是 'codeql' 本身的学习曲线还是比较陡峭的,首先它重新定义了一套查询语法,其次可能是因为它支持的语言过多,导致它的文档极其分散且简单,对新手十分不友好。最后,欢迎对 codeql 在漏洞挖掘场景使用感兴趣的朋友加入我们,一同探讨进步。
本文暂时没有评论,来添加一个吧(●'◡'●)