本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
漏洞概述
Struts2-002 是一个 XSS 漏洞, 该漏洞发生在 <s:url> 和 <s:a > 标签中, 未对标签内字符进行转义, 当标签的属性 includeParams=all 时, 即可触发该漏洞。
漏洞影响版本:
Struts 2.0.0 - Struts 2.1.8.1
更多详情可参考官方通告:
https://cwiki.apache.org/confluence/display/WW/S2-002
环境搭建
直接在上一次 S2-001 的环境上做一些修改
index.jsp 中, 将上次的 form 表单删掉, 添加一个 <s:url> 标签
<%--
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" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<h2>S2-001 Demo</h2>
<p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p>
<s:url action="login" includeParams="all"></s:url>
</body>
</html>
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";
}
}
运行复现漏洞
成功触发 XSS
漏洞分析
当在 JSP 文件中遇到 Struts2 标签 <s: 时, 程序会先调用 doStartTag , 并将标签中的属性设置到对应标签对象相应属性中。最后, 在遇到 /> 结束标签的时候调用 doEndTag 方法。
跟进 component.start 函数
可以看到函数会根据标签 includeParams 属性不同做不同处理,all 和 get 的不同只有一个 mergeRequestParameters 方法, 在 includeParams 为 all 的情况下会调用 mergeRequestParameters 将 tomcat 处取来的参数:
这里取到了我们输入的 payload, 并且保存在 parameters 中。
而在 get 时会调用的两个函数中, includeGetParameters 方法则是先获取请求参数字符串, 再进行处理分割:
而调用 this.req.getQueryString 从 tomcat 处获取的请求参数字符串是经过 URL 编码的, 所以无法造成 XSS 漏洞。
includeExtraParameters 则一般为 null。
执行完 doStartTag 后进入 doEndTag, 调用 buildUrl。
然后返回 result 触发 XSS。
漏洞修复
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("<script>") > 0; result = result.replaceAll("<script>", "script")) {
;
}
在拼接完之后对 script 进行了处理, 但是只是简单的将 script 标签的两个尖括号去掉了, 所以可以轻松绕过:
http://localhost:8080/login.action?<script 1>alert(1)</script>=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