item | detail |
---|---|
Impact of vulnerability | Possible Remote Code Execution |
Maximum security rating | High |
Recommendation | Disable Dynamic Method Invocation if possible. Alternatively upgrade to Struts 2.5 |
Affected Software | Struts 2.3.20-2.3.28 |
Reporter | Alvaro Munoz alvaro dot munoz at hpe dot com |
CVE Identifier | CVE-2016-3087 |
S2-032在修复过程中没有覆盖全面,遗漏了对rest
插件中mapping.setName()
方法传入数据的过滤,使系统开启动态方法调用(Dynamic Method Invocation)时,攻击者仍可构造恶意URL并通过rest插件触发命令执行。
rest插件中的问题代码
\struts-2.3.20.1\src\plugins\rest\src\main\java\org\apache\struts2\rest\RestActionMapper.java
line 181
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
String uri = RequestUtils.getUri(request);
...
parseNameAndNamespace(uri, mapping, configManager);
...
// handle "name!method" convention.
handleDynamicMethodInvocation(mapping, mapping.getName());
该函数通过RequestUtils.getUri()
获取url输入,之后调用parseNameAndNamespace(uri, mapping, configManager)
从url中提取出name值:
protected void parseNameAndNamespace(String uri, ActionMapping mapping,
ConfigurationManager configManager) {
String namespace, name;
int lastSlash = uri.lastIndexOf("/");
if (lastSlash == -1) {
namespace = "";
name = uri;
} else if (lastSlash == 0) {
// ww-1046, assume it is the root namespace, it will fallback to
// default
// namespace anyway if not found in root namespace.
namespace = "/";
name = uri.substring(lastSlash + 1);
} else {
// Try to find the namespace in those defined, defaulting to ""
Configuration config = configManager.getConfiguration();
String prefix = uri.substring(0, lastSlash);
namespace = "";
// Find the longest matching namespace, defaulting to the default
for (Object o : config.getPackageConfigs().values()) {
String ns = ((PackageConfig) o).getNamespace();
if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
if (ns.length() > namespace.length()) {
namespace = ns;
}
}
}
name = uri.substring(namespace.length() + 1);
}
mapping.setNamespace(namespace);
mapping.setName(name);
}
这个从url提取出的name
字段最终通过mapping.setName()
函数,存储在mapping.name
struts-2.3.20.1\src\core\src\main\java\org\apache\struts2\dispatcher\mapper\ActionMapping.java
line 129
public void setName(String name) {
this.name = name;
}
随后,mapping.name
通过handleDynamicMethodInvocation()
方法传入mapping.method
变量。
line 299
private void handleDynamicMethodInvocation(ActionMapping mapping, String name) {
int exclamation = name.lastIndexOf("!");
if (exclamation != -1) {
mapping.setName(name.substring(0, exclamation));
if (allowDynamicMethodCalls) {
mapping.setMethod(name.substring(exclamation + 1));
} else {
mapping.setMethod(null);
}
}
}
在这里,mapping.method
值被带入ognl表达式,命令执行和S2-032相同。
struts-2.3.20.1-all\struts-2.3.20.1\src\xwork-core\src\main\java\com\opensymphony\xwork2\DefaultActionInvocation.java
line 410
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = proxy.getMethod();
if (LOG.isDebugEnabled()) {
LOG.debug("Executing action method = #0", methodName);
}
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Object methodResult;
try {
methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);
} catch (OgnlException e) {
// hmm -- OK, try doXxx instead
try {
String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()";
methodResult = ognlUtil.getValue(altMethodName, ActionContext.getContext().getContextMap(), action);
官方给出的建议:
Disable Dynamic Method Invocation if possible. Alternatively upgrade to Struts 2.3.20.3, Struts 2.3.24.3 or Struts 2.3.28.1.
事实上这三个最新版本并没有修复这个漏洞,在下一版本更新之前建议选用如下方法修复: