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 条评论
登录 后参与评论

相关文章

来自专栏编码小白

ofbiz 服务引擎(一) controller中服务的调用解析

首先根据handler-controller.xml文件中对应handler文件,然后运行RequestHandler中的runEvent方法,方法如下: /*...

38540
来自专栏技术沉淀

Ruby练习一=> {'a' => 3, 'man' => 1, 'canal' => 1, 'panama' => 1, 'plan' => 1}returns the list ["Pam", "

12220
来自专栏高性能服务器开发

(三)dict哈希结构2

dict.c; /* Hash Tables Implementation. * * This file implements in memory ha...

28090
来自专栏码匠的流水账

聊聊storm的IEventLogger

storm-2.0.0/storm-client/src/jvm/org/apache/storm/metric/IEventLogger.java

9520
来自专栏函数式编程语言及工具

Akka-Cluster(2)- distributed pub/sub mechanism 分布式发布/订阅机制

   上期我们介绍了cluster singleton,它的作用是保证在一个集群环境里永远会有唯一一个singleton实例存在。具体使用方式是在集群所有节点部...

16240
来自专栏码匠的流水账

聊聊spring cloud gateway的GlobalFilter

本文主要研究一下spring cloud gateway的GlobalFilter

24910
来自专栏码匠的流水账

FluxSink实例及解析

reactor-core-3.1.3.RELEASE-sources.jar!/reactor/core/publisher/FluxSink.java

21020
来自专栏拂晓风起

cocos2d-js Shader系列2:在cc.Sprite上使用Shader(黑白、灰度、造旧效果)

18140
来自专栏码匠的流水账

聊聊hystrix的fallback

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

77520
来自专栏Golang语言社区

Go中的同步与锁

最近学习了Go语言中同步包中的互斥锁、读写锁、Once、waitGroup。在并发程序开发的过程中,这两种锁是非常重要的,包括对共享资源进行访问控制的时候。sy...

26450

扫码关注云+社区

领取腾讯云代金券