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("\"", "&#34;") : "";
}

对 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/

posted @ 2020-12-14 17:40  twosmi1e  阅读(709)  评论(1编辑  收藏  举报