Struts vulnerability (s2-045)

gamzatti
Vote 0 Votes

- Summary of vulnerabiliry


https://struts.apache.org/docs/s2-045.html



- When a request is conducted to Strunts application, built-in ServletFilter is executed.

   If content_type contains the strings "multipart/form-data",
org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest#parse() is called.
   (which is built in default parser of Struts core)
   So, if we don't modify the defaut settings of parser, attackers can reveledging the 
vulnerability by sending crafted requests contains multipart/form-data in content_type.


- The cause of the vulnerabiliry is the follwing:

     In processs of JakartaMultiPartRequest#parse(), an Exception is thrown and 
buildErrorMessage() is called if content_type is invalid format.
     During handling Exception object in buildErrorMessage(), malicious OGNL specified 
in content_type is executed, which leads to arbitory code execetion.


- Mitigation



- Reject malicious request by using ServletFilter or WAF.

    The following is an example of ServletFilter which retuns server error against malicious 
Content-Type containing OGNL expression.

------

    
    public void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) 
           throws java.io.IOException, javax.servlet.ServletException
    {

        System.out.println("Servlet Filter: "+this.getClass().getName()+"Called.");
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String contentType = httpRequest.getHeader(CONTENT_TYPE);
        String uri = httpRequest.getRequestURI();
        
        BufferedReader reader = null;
        String body = "";
        try{
        	reader = httpRequest.getReader();
        	Stream lines = reader.lines();
        	body = lines.collect(Collectors.joining("\r\n"));
        } catch (IOException e) {
        	// skip filter
        	e.printStackTrace();
        	chain.doFilter(request, response);
        } finally{
        	reader.close();
        }
        
        Pattern p = Pattern.compile(SIGNATURE_OGNL);
        
        if (contentType!=null && !contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART) && 
        		p.matcher(contentType).find()){
        	System.out.println("Malicious Content-Type:"+contentType);
        	throw new ServletException(ERROR_INVALID_REQUEST);
        } else if (p.matcher(uri).find()){
        	System.out.println("Malicious URI:"+uri);
        	throw new ServletException(ERROR_INVALID_REQUEST);
        } else if (p.matcher(body).find()){
        	System.out.println("Malicious Request body:"+body);
        	throw new ServletException(ERROR_INVALID_REQUEST);
        }
        chain.doFilter(request, response);
    }

Example of Servlet filter


------



- Switch parser for multipart/form-data from JakartaMultiPartRequest to another.

     In 2.3.18 later, JakartaStreamMultiPartRequest is available. 
     We can switch parser by setting the following property in struts configuration 
(e.g. struts.xml)
     <constant name="struts.multipart.parser" value="jakarta-stream" />
     Then the follwing class is executed instead of JakartaMultiPartRequest:


https://struts.apache.org/maven/struts2-core/apidocs/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.html

  

2 Comments

| Leave a comment

- Code execution mechanism and notice on security filters

Stack trace in vulnerable codes:

org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter
-->JakartaMultiPartRequest#parse()
--> JakartaMultiPartRequest#buildErrorMessage()
-->LocalizedTextUtil.findText()
-->com.opensymphony.xwork2.util.TextParseUtil
-->translateVariables()
-->parser.evaluate(openChars, expression, ognlEval, maxLoopCount);
--> OgnlTextParser.evaluate()
--> com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecute()
-->Ognl.parseExpression(expression); **

Let's focus on ** : tree = Ognl.parseExpression(expression);
Here is expression example:
(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)....{omitted}
(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))....(#ros.flush())

parseExpression() parses expression and return object containing evaluated ognl codes.
If spaces are contained before and after "."(dot) in expression, they are trimmed during parsing,
meaning that attacker could bypass security filters such as WAF.

If full package name "ognl.OgnlContext" is specified as filter, attackers can bypass it
by sending requests containing spaces for instance "ognl. OgnlContext".

Based on above, we had better to specify only class name as filters
for instance: OgnlContext , OgnlUtil etc.

To record malicious request in Content-Type:
set LogFormat in httpd.conf

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{Content-Type}i\" \"%{Content-Disposition}i\"" combined

Leave a comment

About this Entry

This page contains a single entry by gamzatti published on March 9, 2017 9:36 PM.

Selenium Web Driver for python was the previous entry in this blog.

How to protect Java based web application using mod_security is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.