转载

Struts2框架: S2-002 漏洞详细分析

0x00 前言

阅读本文需要具备的知识:

  1. 熟悉J2EE开发, 主要是JSP开发
  2. 了解Struts2框架执行流程

0x01 漏洞复现

影响漏洞版本:

Struts 2.0.0 - Struts 2.0.11

漏洞靶机代码: (下方通过该代码进行分析, 务必下载本地对比运行)

https://github.com/dean2021/java_security_book/tree/master/Struts2/s2_002

测试POC:

http://localhost:8080/index.action?"><script>alert(1)</script><"

请求响应内容:

<body>

	<a href="//hello/hello_struts2.action?"><script>alert(1)</script><"=&%22%3E%3Cscript%3Ealert(1)%3C/script%3E%3C%22=">ä½ å¥½Struts2</a>

</body>

0x02 漏洞分析

通过官网安全公告 参考[1],我们大概知道问题是出在 标签里,如下是我们的index.jsp部分代码:

<!DOCTYPE html PUBLIC 
	"-//W3C//DTD XHTML 1.1 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	
<%@taglib prefix="s" uri="/struts-tags" %>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<body>
	<a href="<s:url action="/hello/hello_struts2" includeParams="all" ></s:url>">你好Struts2</a>
</body>
</html>

两个标签我们就分析 一个就行了,读过我上篇文章的同学应该知道我们先从找到标签的实现对象入手,这里就不多说了,由于s2的标签库都是集成与ComponentTagSupport类, doStartTag方法也是在该类里实现,所以我们直接从ComponentTagSupport类doStartTag方法进行断点调试, 首先我们看一下doStartTag方法:

public abstract class ComponentTagSupport extends StrutsBodyTagSupport {

	 public int doStartTag() throws JspException {

	 	// 实现子类是URL.class
        this.component = this.getBean(this.getStack(), (HttpServletRequest)this.pageContext.getRequest(), (HttpServletResponse)this.pageContext.getResponse());
        Container container = Dispatcher.getInstance().getContainer();
        container.inject(this.component);
        this.populateParams();

        // 跟进URL类的start方法实现
        boolean evalBody = this.component.start(this.pageContext.getOut());
        if (evalBody) {
            return this.component.usesBody() ? 2 : 1;
        } else {
            return 0;
        }
    }

跟进URL类的start方法实现:

public class URL extends Component {

	public boolean start(Writer writer) {
	        boolean result = super.start(writer);
	        if (this.value != null) {
	            this.value = this.findString(this.value);
	        }

	        try {

	        	// 我们在<s:url>这个标签内配置的includeParams="all"
	        	// 关于这个属性介绍,参考2
	            String includeParams = this.urlIncludeParams != null ? this.urlIncludeParams.toLowerCase() : "get";
	            if (this.includeParams != null) {
	                includeParams = this.findString(this.includeParams);
	            }


	            if ("none".equalsIgnoreCase(includeParams)) {
	                this.mergeRequestParameters(this.value, this.parameters, Collections.EMPTY_MAP);
	            } else if ("all".equalsIgnoreCase(includeParams)) {

                    // 我们跟进此方法的实现
	                this.mergeRequestParameters(this.value, this.parameters, this.req.getParameterMap());
	                
	                this.includeGetParameters();
	                this.includeExtraParameters();
	            } else if (!"get".equalsIgnoreCase(includeParams) && (includeParams != null || this.value != null || this.action != null)) {
	                if (includeParams != null) {
	                    LOG.warn("Unknown value for includeParams parameter to URL tag: " + includeParams);
	                }
	            } else {
	                this.includeGetParameters();
	                this.includeExtraParameters();
	            }
	        } catch (Exception var4) {
	            LOG.warn("Unable to put request parameters (" + this.req.getQueryString() + ") into parameter map.", var4);
	        }

	        return result;
	    }

this.mergeRequestParameters(this.value, this.parameters, this.req.getParameterMap()); 跟进实现:

protected void mergeRequestParameters(String value, Map parameters, Map contextParameters) {
        Map mergedParams = new LinkedHashMap(contextParameters);
        if (value != null && value.trim().length() > 0 && value.indexOf("?") > 0) {
            new LinkedHashMap();
            String queryString = value.substring(value.indexOf("?") + 1);
            mergedParams = UrlHelper.parseQueryString(queryString);
            Iterator iterator = contextParameters.entrySet().iterator();

            while(iterator.hasNext()) {
                Entry entry = (Entry)iterator.next();
                Object key = entry.getKey();
                if (!((Map)mergedParams).containsKey(key)) {
                    ((Map)mergedParams).put(key, entry.getValue());
                }
            }
        }

        Iterator iterator = ((Map)mergedParams).entrySet().iterator();

        while(iterator.hasNext()) {
            Entry entry = (Entry)iterator.next();
            Object key = entry.getKey();
            if (!parameters.containsKey(key)) {
                parameters.put(key, entry.getValue());
            }
        }

}

从方法明明上我们已经能够看得出该方法是合并参数,通过阅读代码该方法的第三个参数也就是HttpServletRequest对象getParameterMap(), HttpServletRequest是Servlet原生对象,那这个方法具体是用来做什么的呢?下方是官方解释:

Returns a java.util.Map of the parameters of this request.

也就是返回一个map类型的request参数。我们请求的是url是:

http://localhost:8080/index.action?"><script>alert(1)</script><"

那么解析后的map就是 : KEY = "><script>alert(1)</script><" VAL = “” 然后进行参数合并, 并未看到对参数进行任何过滤,最后写入到html中,导致造成xss漏洞。

TIPS: 经过测试HttpServletRequest对象getParameterMap()方法只会对参数值进行转换编码,并不会对参数名进行任何处理.

0x03 总结:

Struts2框架的<s:url>标签的includeParams属性设置为all的情况下,对url参数名未做过滤,导致xss漏洞。

0x04 修复方案分析:

根据公告,我们需要升级到Struts 2.0.11.1版本。

经过对2.0.11.1的代码阅读,在UrlHelper类buildUrl方法里,第136行增加了如下修复代码:

// link是最终的生成的url
        for(result = link.toString(); result.indexOf("<script>") > 0; result = result.replaceAll("<script>", "script")) {

        }

看到这样的修复,虽然很无语,但是站在没有web安全知识的程序员角度来看待这种修复方案,能这样写也是很正常,因为大部分程序员只知道JavaScript代码是在 <script> 标签中执行。

好了,分析结束,附上一个bypass POC:

index.action?"><script 1>alert(1)</script>"

下一篇S2-003应该就是修复这个问题吧。

0x06 引用

原文  https://dean2021.github.io/posts/s2-002/
正文到此结束
Loading...