> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/q1f3mhrdYcS7jfQULBAQDQ) 漏洞概述 ![](https://mmbiz.qpic.cn/mmbiz_png/P8alCrBvaf8GuUDaiarbTO9wddus1Fjx5ZBXRP38ZKvLHR3lb3PJwFOY5EKmTEiaiaMUianL2qV3U96J3gXqR9L7Jw/640?wx_fmt=png) Struts2-002 是一个 XSS 漏洞, 该漏洞发生在 标签中, 未对标签内字符进行转义, 当标签的属性 includeParams=all 时, 即可触发该漏洞。 漏洞影响版本: Struts 2.0.0 - Struts 2.1.8.1  更多详情可参考官方通告: https://cwiki.apache.org/confluence/display/WW/S2-002 环境搭建 ![](https://mmbiz.qpic.cn/mmbiz_png/P8alCrBvaf8GuUDaiarbTO9wddus1Fjx5ZBXRP38ZKvLHR3lb3PJwFOY5EKmTEiaiaMUianL2qV3U96J3gXqR9L7Jw/640?wx_fmt=png) 直接在上一次 S2-001 的环境上做一些修改 index.jsp 中, 将上次的 form 表单删掉, 添加一个 标签 ``` <%-- Created by IntelliJ IDEA. User: twosmi1e Date: 2020/11/19 Time: 7:32 下午 To change this template use File | Settings | File Templates. --%> <%@ page language="java" contentType="text/html" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags" %> S2-001

S2-001 Demo

link: https://cwiki.apache.org/confluence/display/WW/S2-001

``` LoginAction.java 中添加 url 变量, 将对用户名密码的处理删掉 ``` package com.demo.action; import com.opensymphony.xwork2.ActionSupport; public class LoginAction extends ActionSupport { private String username = null; private String password = null; private String url = null; public String getUsername() { return this.username; } public String getPassword() { return this.password; } public String getUrl(){ return this.url; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setUrl(String url){ this.url = url; } public String execute() throws Exception { return "error"; } } ``` 运行复现漏洞 ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15icgpYnMe8WuQBT7lTp4MkhGDM0q5J3knAu7fhCo6mibGs3DOFoHQReew/640?wx_fmt=png) 成功触发 XSS 漏洞分析 ![](https://mmbiz.qpic.cn/mmbiz_png/P8alCrBvaf8GuUDaiarbTO9wddus1Fjx5ZBXRP38ZKvLHR3lb3PJwFOY5EKmTEiaiaMUianL2qV3U96J3gXqR9L7Jw/640?wx_fmt=png) 当在 JSP 文件中遇到 Struts2 标签 结束标签的时候调用 doEndTag 方法。 ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15D93p298icZM8UiaFOPCTV4HuqHnyqTY4elDRnNO4ajrtCS6GkHaNrHGQ/640?wx_fmt=png) 跟进 component.start 函数 ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15deoibibycpv55S582cclTP4o1CXqH6TENec4Ban9l9eibz7TdjL1MlS8g/640?wx_fmt=png) 可以看到函数会根据标签 includeParams 属性不同做不同处理,all 和 get 的不同只有一个 mergeRequestParameters 方法, 在 includeParams 为 all 的情况下会调用 mergeRequestParameters 将 tomcat 处取来的参数: ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15drfeFmBRQYcPZoRZzQKuToOHPchToVJVOnC35DuTu7bqGSYIuiaWkhw/640?wx_fmt=png) 这里取到了我们输入的 payload, 并且保存在 parameters 中。 ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15ETl1SM7M2t1eDzVaIebujBITCMniaFXKvPZtPP4nGleib2rc2UcicZibTQ/640?wx_fmt=png) ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15H6KbiabaFUV1HlDicNCnk5HkKh32sIYicfCUtXeQlNCpcUtvOAHNEqnKA/640?wx_fmt=png) ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V159LBaqdXoQUgiacxBdZVpwO4amdTE4egn4YKys0hVfibvsHwz49IvZVzA/640?wx_fmt=png) 而在 get 时会调用的两个函数中, includeGetParameters 方法则是先获取请求参数字符串, 再进行处理分割: ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15DH7jrhRvGtcOJslRjNbibGtEPNPDk7qCTYc2iaOUmTXDrnHMwLibLFrLw/640?wx_fmt=png) 而调用 this.req.getQueryString 从 tomcat 处获取的请求参数字符串是经过 URL 编码的, 所以无法造成 XSS 漏洞。 includeExtraParameters 则一般为 null。 执行完 doStartTag 后进入 doEndTag, 调用 buildUrl。 ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15ld7fbUS2fMSDhQPRFOhCB14W3NeOelgFiasUFsE5gCibiaZDBxPiaDpZvQ/640?wx_fmt=png) ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V153ygh7p9256lGcWTBaqQdgGszKsPudEHwvSla2GEvF9V6tW2z0J7QmA/640?wx_fmt=png) 然后返回 result 触发 XSS。 ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15IVIWu3Cfibfxl45LBJysLJ4mywY8cPnHLrStfw87mZlhyWw45fRDNsQ/640?wx_fmt=png) ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15eAfnwx2vXlfEO7voe2YCAQVbVZBI9qHRIe46sqdsKrd5Hw3g8WCFpA/640?wx_fmt=png) ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icflukJibHwU6ticibFoDo6V15BxdlwRF3lj91aViakOtxibufyUWHy0yltWnia4bx94UyFOicKVQHyv0m4g/640?wx_fmt=png) 漏洞修复 ![](https://mmbiz.qpic.cn/mmbiz_png/P8alCrBvaf8GuUDaiarbTO9wddus1Fjx5ZBXRP38ZKvLHR3lb3PJwFOY5EKmTEiaiaMUianL2qV3U96J3gXqR9L7Jw/640?wx_fmt=png) **2.0.11.1 版本修复** 对 s:a 标签的修复如下: ``` if (this.href != null) { this.addParameter("href", this.ensureAttributeSafelyNotEscaped(this.findString(this.href))); } 加上了一个 ensureAttributeSafelyNotEscaped 方法来过滤双引号: protected String ensureAttributeSafelyNotEscaped(String val) { return val != null ? val.replaceAll("\"", """) : ""; } ``` 对 s:url 标签的修复如下: ``` for(result = link.toString(); result.indexOf("=hello ``` **2.2.1 版本修复** ``` private static String buildParameterSubstring(String name, String value) { StringBuilder builder = new StringBuilder(); builder.append(translateAndEncode(name)); builder.append('='); builder.append(translateAndEncode(value)); return builder.toString(); } public static String translateAndEncode(String input) { String translatedInput = translateVariable(input); String encoding = getEncodingFromConfiguration(); try { return URLEncoder.encode(translatedInput, encoding); } catch (UnsupportedEncodingException e) { LOG.warn("Could not encode URL parameter '" + input + "', returning value un-encoded"); return translatedInput; } } ``` 使用 url 编码修复了漏洞。 **参考** https://aluvion.gitee.io/2020/07/15/struts2 系列漏洞 - S2-002/ https://mochazz.github.io/2020/06/17/Java 代码审计之 Struts2-002/ end ![](https://mmbiz.qpic.cn/mmbiz_png/RXib24CCXQ0icgJnwz55vaCiatpsqriaW2GZ7rRw3kbvpDFicsKcLcp9Q7tYiaMwLANvcHAoByTiaGaus4HBukgfIXt9g/640?wx_fmt=png)