CVE-2018-11776:如何通过Semmle QL找到Apache Struts的远程执行
前言
2018年4月,一个新的Apache Struts远程代码执行漏洞被报告。在Struts特定配置下,访问特制的URL可以触发该漏洞。漏洞编号为CVE-2018-11776(S2-057)。本文将介绍发现漏洞的过程。
映射攻击面
许多漏洞涉及从不受信任的源(例如,用户输入)流向某个特定位置的数据,这种情况下数据会以危险的方式被利用(SQL查询,反序列化以及一些其他解释语言等等)。对于特定项目,开始着手研究此类问题的一种好方法是查看旧版本软件的已知漏洞。这可以让你深入了解所想要查找的各种源及接收点。
在本案例中,作者首先查看了RCE漏洞S2-032(CVE-2016-3081),S2-033(CVE-2016-3687)和S2-037(CVE-2016-4438)。与Struts中的许多其他RCE一样,这些RCE涉及被认为是OGNL表达式的非法输入,允许攻击者在服务器上运行任意代码。这三个漏洞特别有趣,不仅因为它们让我们对Struts的内部工作有了一些了解,而且还因为同样的问题需要三次才能修复!
所有这三个问题都是远程输入通过变量methodName作为参数传递给方法OgnlUtil::getValue()的结果。
String methodName = proxy.getMethod(); //source, but where from?
LOG.debug("Executing action method = {}", methodName);
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Object methodResult;
try {
methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); //
这里的proxy字段是ActionProxy类型,它是一个接口。在查看它的定义时,作者发现除了方法getMethod()(在上面的代码中用于赋值的变量methodName)之外,还有各种其他的方法,如getActionName()和getNamespace()。这些方法貌似会从URL返回信息。所以作者开始假设所有这些方法都可能返回不受信任的输入。
现在可以使用QL开始对这些不受信任的来源进行建模:
class ActionProxyGetMethod extends Method {
ActionProxyGetMethod() {
getDeclaringType().getASupertype*().hasQualifiedName("com.opensymphony.xwork2", "ActionProxy") and
(
hasName("getMethod") or
hasName("getNamespace") or
hasName("getActionName")
)
}
}
predicate isActionProxySource(DataFlow::Node source) {
source.asExpr().(MethodAccess).getMethod() instanceof ActionProxyGetMethod
}
识别OGNL接收点
现在已经识别并描述了一些不受信任的来源,下一步是为接收点做同样的事情。如前所述,许多Struts RCE涉及将远程输入解析为OGNL表达式。Struts中有许多函数最终将它们的参数作为OGNL表达式进行评估;对于在本文中开始的三个漏洞,都使用了OgnlUtil::getValue(),但是在漏洞S2-045(CVE-2017-5638)中,使用了TextParseUtil::translateVariables()。我们可以寻找用于执行OGNL表达式的函数,而不是将每一个方法描述为QL中的单独接收点。而OgnlUtil::compileAndExecute()和OgnlUtl::compileAndExecuteMethod()看起来像有希望的接收点。
作者在一个QL predicate中描述了它们,如下所示:
predicate isOgnlSink(DataFlow::Node sink) {
exists(MethodAccess ma | ma.getMethod().hasName("compileAndExecute") or ma.getMethod().hasName("compileAndExecuteMethod") |
ma.getMethod().getDeclaringType().getName().matches("OgnlUtil") and
sink.asExpr() = ma.getArgument(0)
)
}
第一次尝试污点跟踪
现在已经在QL中定义了源和接收点,所以可以在污点跟踪查询中使用这些定义。 这里作者使用DataFlow库来定义DataFlow配置:
class OgnlTaintTrackingCfg extends DataFlow::Configuration {
OgnlTaintTrackingCfg() {
this = "mapping"
}
override predicate isSource(DataFlow::Node source) {
isActionProxySource(source)
}
override predicate isSink(DataFlow::Node sink) {
isOgnlSink(sink)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
TaintTracking::localTaintStep(node1, node2) or
exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and
node1.asExpr().getEnclosingCallable().getDeclaringType() = t and
node2.asExpr().getEnclosingCallable().getDeclaringType() = t
)
}
}
from OgnlTaintTrackingCfg cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select source, sink
这里作者使用了之前定义的isActionProxySource和isOgnlSink predicates。
需要注意的是,作者还覆盖了一个名为isAdditionalFlowStep的predicate。这个predicate允许包含可以传播非法数据的额外步骤。例如,这允许将项目特定的信息合并到流配置中。再如,如果有通过某个网络层进行通信的组件,则可以在QL中描述那些各种网络端点的代码,允许DataFlow库跟踪非法数据。
对于此特定查询,作者添加了两个额外的流程步骤供DataFlow库使用。 第一个:
TaintTracking::localTaintStep(node1, node2)
前言
2018年4月,一个新的Apache Struts远程代码执行漏洞被报告。在Struts特定配置下,访问特制的URL可以触发该漏洞。漏洞编号为CVE-2018-11776(S2-057)。本文将介绍发现漏洞的过程。
映射攻击面
许多漏洞涉及从不受信任的源(例如,用户输入)流向某个特定位置的数据,这种情况下数据会以危险的方式被利用(SQL查询,反序列化以及一些其他解释语言等等)。对于特定项目,开始着手研究此类问题的一种好方法是查看旧版本软件的已知漏洞。这可以让你深入了解所想要查找的各种源及接收点。
在本案例中,作者首先查看了RCE漏洞S2-032(CVE-2016-3081),S2-033(CVE-2016-3687)和S2-037(CVE-2016-4438)。与Struts中的许多其他RCE一样,这些RCE涉及被认为是OGNL表达式的非法输入,允许攻击者在服务器上运行任意代码。这三个漏洞特别有趣,不仅因为它们让我们对Struts的内部工作有了一些了解,而且还因为同样的问题需要三次才能修复!
所有这三个问题都是远程输入通过变量methodName作为参数传递给方法OgnlUtil::getValue()的结果。
String methodName = proxy.getMethod(); //source, but where from? 无奈人生安全网
LOG.debug("Executing action method = {}", methodName);
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Object methodResult;
try {
methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); //
这里的proxy字段是ActionProxy类型,它是一个接口。在查看它的定义时,作者发现除了方法getMethod()(在上面的代码中用于赋值的变量methodName)之外,还有各种其他的方法,如getActionName()和getNamespace()。这些方法貌似会从URL返回信息。所以作者开始假设所有这些方法都可能返回不受信任的输入。
现在可以使用QL开始对这些不受信任的来源进行建模:
class ActionProxyGetMethod extends Method {
ActionProxyGetMethod() {
getDeclaringType().getASupertype*().hasQualifiedName("com.opensymphony.xwork2", "ActionProxy") and
( 无奈人生安全网
hasName("getMethod") or
hasName("getNamespace") or
hasName("getActionName")
)
}
}
predicate isActionProxySource(DataFlow::Node source) {
source.asExpr().(MethodAccess).getMethod() instanceof ActionProxyGetMethod
}
识别OGNL接收点
现在已经识别并描述了一些不受信任的来源,下一步是为接收点做同样的事情。如前所述,许多Struts RCE涉及将远程输入解析为OGNL表达式。Struts中有许多函数最终将它们的参数作为OGNL表达式进行评估;对于在本文中开始的三个漏洞,都使用了OgnlUtil::getValue(),但是在漏洞S2-045(CVE-2017-5638)中,使用了TextParseUtil::translateVariables()。我们可以寻找用于执行OGNL表达式的函数,而不是将每一个方法描述为QL中的单独接收点。而OgnlUtil::compileAndExecute()和OgnlUtl::compileAndExecuteMethod()看起来像有希望的接收点。
作者在一个QL predicate中描述了它们,如下所示:
predicate isOgnlSink(DataFlow::Node sink) { www.wnhack.com
exists(MethodAccess ma | ma.getMethod().hasName("compileAndExecute") or ma.getMethod().hasName("compileAndExecuteMethod") |
ma.getMethod().getDeclaringType().getName().matches("OgnlUtil") and
sink.asExpr() = ma.getArgument(0)
)
}
第一次尝试污点跟踪
现在已经在QL中定义了源和接收点,所以可以在污点跟踪查询中使用这些定义。 这里作者使用DataFlow库来定义DataFlow配置:
class OgnlTaintTrackingCfg extends DataFlow::Configuration {
OgnlTaintTrackingCfg() {
this = "mapping"
}
override predicate isSource(DataFlow::Node source) {
isActionProxySource(source)
}
override predicate isSink(DataFlow::Node sink) {
isOgnlSink(sink)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
TaintTracking::localTaintStep(node1, node2) or 内容来自无奈安全网
exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and
node1.asExpr().getEnclosingCallable().getDeclaringType() = t and
node2.asExpr().getEnclosingCallable().getDeclaringType() = t
)
}
}
from OgnlTaintTrackingCfg cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select source, sink
这里作者使用了之前定义的isActionProxySource和isOgnlSink predicates。
需要注意的是,作者还覆盖了一个名为isAdditionalFlowStep的predicate。这个predicate允许包含可以传播非法数据的额外步骤。例如,这允许将项目特定的信息合并到流配置中。再如,如果有通过某个网络层进行通信的组件,则可以在QL中描述那些各种网络端点的代码,允许DataFlow库跟踪非法数据。
对于此特定查询,作者添加了两个额外的流程步骤供DataFlow库使用。 第一个:
TaintTracking::localTaintStep(node1, node2)