Struts 2 漏洞系列之 S2-002

漏洞原理

    由于 Struts 2 框架在处理 <s:url> 和 <s:a> 两个标签时,未对标签内的字符进行有效转义,导致存在 XSS 注入漏洞。该漏洞主要有两个利用场景如下:

  • 场景一:在 <s:a> 标签中,注入未转义的双引号导致 XSS 漏洞。
  • 场景二:在 <s:url> 和 <s:a> 标签中,当参数 includeParams 的值不为 “none” 时,不会对 <script> 标签进行转义,导致 XSS 漏洞。

    官方给出的 POC 是:http://localhost/foo/bar.action?<script>alert(1)</script>test=hello

    我们知道标签一般分为两大类,一类是通用型的,另一类是界面( UI )型的。通用标签中,又分控制标签和数据标签两种。而此处所提到的 <s:a> 和 <s:url> 实际上都是属于 Struts 2 框架中的两种数据标签。其中,<s:a> 标签负责处理超链接,生成 HTML 中的 <a> 标签。

    早先的 Struts 1 框架中的是通过解析 EL(Expression Language)表达式来计算超链接地址的,在 Struts 2.0.0.11 之后就不再支持 EL(Expression Language)表达式,改用 OGNL 表达式,<s:url> 标签的作用之一就是将 OGNL 表达式的计算结果传递给 <s:a> 标签,以便生成超链接地址。使用这两个标签前,要先用这个姿势引入标签库:

1
<%@ taglib prefix = "s" uri = "/struts-tags"%>

    解释完标签,我们再来看看 includeParams 属性的作用。<s:url> 和 <s:a> 两个标签中都有 includeParams 属性,该属性主要作用是指定在何种情况下 HTTP request 请求需要包含参数。在全局配置中默认为 none,配置信息如下:

1
2
3
4
5
<struts>
...
<constant name="struts.url.includeParams" value="none" />
...
</struts>

    该属性有三个值可供选择:allget 和 none。当该属性设置为 all 时,表示不管 GET 或 POST 请求,该 URL 都需要包含参数;当该属性设置为 get 时,表示仅在 GET 请求时,该 URL 才需要包含参数;当该属性设置为 none 时,表示不论 GET 或 POST 请求,该 URL 都不需要包含参数。

    因此,场景二就不难理解,如果 includeParams 属性设置为 none 了,所注入的地址就会不接受参数而导致 XSS 漏洞无法有效触发。

修复代码

原始漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//if the action was not explicitly set grab the params from the request
if (escapeAmp) {
buildParametersString(params, link);
} else {
buildParametersString(params, link, "&");
}
String result;
try {
result = encodeResult ? response.encodeURL(link.toString()) : link.toString();
} catch (Exception ex) {
// Could not encode the URL for some reason
// Use it unchanged
result = link.toString();
}

官方补丁代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//if the action was not explicitly set grab the params from the request
if (escapeAmp) {
buildParametersString(params, link);
} else {
buildParametersString(params, link, "&");
}
String result = link.toString();
while (result.indexOf("<script>") > 0) {
result = result.replaceAll("<script>", "script");
}
try {
result = encodeResult ? response.encodeURL(result) : result;
} catch (Exception ex) {
// Could not encode the URL for some reason
// Use it unchanged
result = link.toString();
}

    可以看到,官方修复的做法是,循环检测 <s:url> 和 <s:a> 标签内的数据,如果存在 <script> 就将其替换为 script ,去掉了 < 和 > 字符,直到不存在 <script> 标签为止。

    根据阿里巴巴安全专家吴翰清的 《白帽子讲 WEB 安全》 书中所述,刚开始官方修复的方式是用 if 简单的去掉一层 <script> 标签,直到有人提出可以构造 <<script>> 或 <<<script>>> 这样的标签来绕过官方的补丁,后来官方发布的补丁中仅仅是把 if 换成了 while。然而,吴翰清提出,这样的修复并不完善,仍然可以通过下面的方法绕过:

http://localhost/foo/bar.action?<script test=hello>alert(1)</script>test=hello

挖洞思路

  • 需要了解 Struts 2 框架的常用标签。
  • 需要熟悉 XSS 的原理。

参考链接