Struts2 S2-045 漏洞触发流程不严谨推测

// 根据 已有的一些信息和修复版本的代码,推测应该是如下的触发流程
// 因为没有测试环境,也只是不严谨的代码触发流程推测,不保证正确性,欢迎大神交流分享。

//core\src\main\java\org\apache\struts2\dispatcher\multipart\JakartaMultiPartRequest.java
public void parse(HttpServletRequest request, String saveDir) throws IOException
{
    try
    {
        setLocale(request);
        processUpload(request, saveDir); // 调用 1
    }
    catch (FileUploadBase.SizeLimitExceededException e)
    {
        if (LOG.isWarnEnabled())
        {
            LOG.warn("Request exceeded size limit!", e);
        }
        String errorMessage = buildErrorMessage(e, new Object[] {e.getPermittedSize(), e.getActualSize()});
        if (!errors.contains(errorMessage))
        {
            errors.add(errorMessage);
        }
    }
    catch (Exception e)     // 调用 7 捕获异常
    {
        if (LOG.isWarnEnabled())
        {
            LOG.warn("Unable to parse request", e);
        }
        String errorMessage = buildErrorMessage(e, new Object[] {}); //调用 8
        if (!errors.contains(errorMessage))
        {
            errors.add(errorMessage);
        }
    }
}

protected String buildErrorMessage(Throwable e, Object[] args)
{
    String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName();
    if (LOG.isDebugEnabled())
    {
        LOG.debug("Preparing error message for key: [#0]", errorKey);
    }
    return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
    // 调用9,触发
   // 因为 args 为空,使用默认的 e.getMessage() 为 "the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is" + content-type
   // If a message is found, it will also be interpolated. Anything within ${...} will be treated as an OGNL expression and evaluated as such.
  // 故执行了http 请求头content-type 中的{}内部引入的指令
// reference:https://struts.apache.org/maven/struts2-core/apidocs/com/opensymphony/xwork2/util/LocalizedTextUtil.html#findText(java.lang.Class, java.lang.String, java.util.Locale, java.lang.String, java.lang.Object[])
}

protected void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException
{
    for (FileItem item : parseRequest(request, saveDir))   //  调用2,如下所示
    {
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Found item " + item.getFieldName());
        }
        if (item.isFormField())
        {
            processNormalFormField(item, request.getCharacterEncoding());
        }
        else
        {
            processFileField(item);
        }
    }
}

protected List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException
{
    DiskFileItemFactory fac = createDiskFileItemFactory(saveDir);
    ServletFileUpload upload = createServletFileUpload(fac);
    return upload.parseRequest(createRequestContext(servletRequest)); // 调用 2.1
}

protected ServletFileUpload createServletFileUpload(DiskFileItemFactory fac)
{
    ServletFileUpload upload = new ServletFileUpload(fac);
    upload.setSizeMax(maxSize);
    return upload;
}

// commons - fileupload - 1.2.1.jar org.apache.commons.fileupload.servlet ServletFileUpload.java
public class ServletFileUpload extends FileUpload
{
    public static final boolean isMultipartContent(final HttpServletRequest request)
    {
        if (!"post".equals(request.getMethod().toLowerCase()))
        {
            return false;
        }
        final String contentType = request.getContentType();
        return contentType != null && contentType.toLowerCase().startsWith("multipart/");
    }

    public ServletFileUpload()
    {
    }

    public ServletFileUpload(final FileItemFactory fileItemFactory)
    {
        super(fileItemFactory);
    }

    public List parseRequest(final HttpServletRequest request) throws FileUploadException
    {
        return this.parseRequest(new ServletRequestContext(request)); // 调用 2.2
    }

    public FileItemIterator getItemIterator(final HttpServletRequest request) throws FileUploadException, IOException
    {
        return super.getItemIterator(new ServletRequestContext(request));
    }
}



// commons - fileupload - 1.2.1.jar org.apache.commons.fileupload FileUploadBase.java
public List parseRequest(final RequestContext ctx) throws FileUploadException
{
    try
    {
        final FileItemIterator iter = this.getItemIterator(ctx); // 调用 3
        final List items = new ArrayList();
        final FileItemFactory fac = this.getFileItemFactory();
        if (fac == null)
        {
            throw new NullPointerException("No FileItemFactory has been set.");
        }
        while (iter.hasNext())
        {
            final FileItemStream item = iter.next();
            final FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), item.getName());
            try
            {
                Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
            }
            catch (FileUploadIOException e)
            {
                throw (FileUploadException)e.getCause();
            }
            catch (IOException e2)
            {
                throw new IOFileUploadException("Processing of multipart/form-data request failed. " + e2.getMessage(), e2);
            }
            if (fileItem instanceof FileItemHeadersSupport)
            {
                final FileItemHeaders fih = item.getHeaders();
                ((FileItemHeadersSupport)fileItem).setHeaders(fih);
            }
            items.add(fileItem);
        }
        return items;
    }
    catch (FileUploadIOException e3)
    {
        throw (FileUploadException)e3.getCause();
    }
    catch (IOException e4)
    {
        throw new FileUploadException(e4.getMessage(), e4);
    }
}

public FileItemIterator getItemIterator(final RequestContext ctx) throws FileUploadException, IOException
{
    return new FileItemIteratorImpl(ctx); //调用 4
}

private class FileItemIteratorImpl implements FileItemIterator
{
    private final MultipartStream multi;
    private final MultipartStream.ProgressNotifier notifier;
    private final byte[] boundary;
    private FileItemStreamImpl currentItem;
    private String currentFieldName;
    private boolean skipPreamble;
    private boolean itemValid;
    private boolean eof;
    private final /* synthetic */ FileUploadBase this$0;

    // 构造函数调用 5
    FileItemIteratorImpl(final RequestContext ctx) throws FileUploadException, IOException
    {
        if (ctx == null)
        {
            throw new NullPointerException("ctx parameter");
        }
        final String contentType = ctx.getContentType();
        if (null == contentType || !contentType.toLowerCase().startsWith("multipart/"))
        {
            throw new InvalidContentTypeException("the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is " + contentType);
            // 调用6,抛出异常
        }
        InputStream input = ctx.getInputStream();
        if (FileUploadBase.this.sizeMax >= 0L)
        {
            final int requestSize = ctx.getContentLength();
            if (requestSize == -1)
            {
                input = new LimitedInputStream(input, FileUploadBase.this.sizeMax)
                {
                    protected void raiseError(final long pSizeMax, final long pCount) throws IOException
                    {
                        final FileUploadException ex = new SizeLimitExceededException("the request was rejected because its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax);
                        throw new FileUploadIOException(ex);
                    }
                };
            }
            else if (FileUploadBase.this.sizeMax >= 0L && requestSize > FileUploadBase.this.sizeMax)
            {
                throw new SizeLimitExceededException("the request was rejected because its size (" + requestSize + ") exceeds the configured maximum (" + FileUploadBase.this.sizeMax + ")", requestSize, FileUploadBase.this.sizeMax);
            }
        }
        String charEncoding = FileUploadBase.this.headerEncoding;
        if (charEncoding == null)
        {
            charEncoding = ctx.getCharacterEncoding();
        }
        this.boundary = FileUploadBase.this.getBoundary(contentType);
        if (this.boundary == null)
        {
            throw new FileUploadException("the request was rejected because no multipart boundary was found");
        }
        this.notifier = new MultipartStream.ProgressNotifier(FileUploadBase.this.listener, ctx.getContentLength());
        (this.multi = new MultipartStream(input, this.boundary, this.notifier)).setHeaderEncoding(charEncoding);
        this.skipPreamble = true;
        this.findNextItem();
    }

    //......
}


// commons - fileupload - 1.2.1.jar org.apache.commons.fileupload FileUploadException.java
public FileUploadException(final String msg, final Throwable cause)
{
    super(msg);
    this.cause = cause;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊hystrix的fallback

hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/AbstractCommand.java

752
来自专栏岑玉海

Hbase 学习(二)各种filter

各种filter 今天的主题是Filter,hbase客户端查询的时候,自定义查询filter。 直接上例子吧,不多说别的了,第一个例子是RowFilte...

2643
来自专栏ml

朴素贝叶斯分类器(离散型)算法实现(一)

1. 贝叶斯定理:        (1)   P(A^B) = P(A|B)P(B) = P(B|A)P(A)   由(1)得    P(A|B) = P(B|...

3117
来自专栏码匠的流水账

聊聊storm的ack机制

storm-2.0.0/storm-client/src/jvm/org/apache/storm/executor/spout/SpoutOutputColl...

821
来自专栏菩提树下的杨过

javascript实现数字转大写金额的函数

<SCRIPT type="text/javascript" Charset="GB2312"> function convertCurrency(curre...

1959
来自专栏ml

Hbase关于Java常用API举例

1. HBase相关对Admin操作的的API封装在HBaseAdmin中,封装了HBase常用操作的API 使用方法:     pom.xml <!-- ...

4189
来自专栏Albert陈凯

第一个参数:initScans(job)

第一个参数:initScans(job) private List<Scan> initScans(Job job) { // 时间戳+.......

3087
来自专栏Jed的技术阶梯

HBase Java API 03:HBase与MapReduce整合

编写MapReduce程序,把"student"表中"info"列族下的"name"那一列抽取出来,存入新HBase表"student_extract"中,要求...

692
来自专栏xingoo, 一个梦想做发明家的程序员

Hbase多版本的读写(Shell&Java API版)

Hbase是基于HDFS的NOsql数据库,它很多地方跟数据库差不多,也有很多不同的地方。这里就不一一列举了,不过Hbase有个版本控制的特性,这个特性在很多...

2259
来自专栏有困难要上,没有困难创造困难也要上!

HBase客户端API-过滤器列表

3238

扫码关注云+社区