S2-002漏洞分析
漏洞概述
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
中
在 mergeRequestParameters 方法之后,依次是 includeGetParameters、includeExtraParameters 方法。 includeGetParameters 方法主要是将 HttpRequest.getQueryString() 的数据存入 this.parameters 属性,而 includeExtraParameters 方法则用来存入其它数据。
而在 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/