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 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;
}
 
  


我的博客即将同步至腾讯云+社区,邀请大家一同入驻。


你可能感兴趣的:(Web安全)