Struts2 S2-045 漏洞觸發流程不嚴謹推測

s1mba發表於2017-03-07
// 根據 已有的一些資訊和修復版本的程式碼,推測應該是如下的觸發流程
// 因為沒有測試環境,也只是不嚴謹的程式碼觸發流程推測,不保證正確性,歡迎大神交流分享。

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


我的部落格即將同步至騰訊雲+社群,邀請大家一同入駐。


相關文章