Apache Struts 2 Remote Code Execution Vulnerability (S2-046) Technical Analysis and Solution

In the wee hours of March 21, Apache Struts 2 released a security bulletin, announcing a remote code execution (RCE) vulnerability in the Jakarta Multipart parser, which has been assigned CVE-2017-5638.

An attacker could exploit this vulnerability to trigger an exception by setting the filename field in Content-Disposition maliciously or the Content-Length header improperly (greater than 2 GB), and cause the OGNL expression in the filename field to be executed, thereby launching a remote attack. The cause and mechanism of this vulnerability are the same as those of S2-045 (with the same CVE ID), but it is different from the latter in the vector.

Related information is available in the following links:

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

https://cwiki.apache.org/confluence/display/WW/S2-046

Affected Versions

  • Struts 2.3.5 – Struts 2.3.31
  • Struts 2.5 – Struts 2.5.10

Unaffected Versions

  • Struts 2.3.32
  • Struts 2.5.10.1

Geographic Distribution of This Struts 2 Vulnerability Provided by NSFOCUS Threat Intelligence

  • Global Distribution

  • Nationwide Distribution in China

  • Global Rankings

  • Rankings in China

Vulnerability Analysis

  • Description

Apache Struts 2 is prone to an RCE vulnerability. An attacker could inject malicious code in the filename field of the HTTP header Content-Disposition and then trigger an exception with a malicious filename value or improper Content-Length header (greater than 2 GB). Successful exploitation of this vulnerability would allow arbitrary code execution.

  • Detailed Analysis

The official description of this vulnerability is as follows:

It is possible to perform a RCE attack with a malicious Content-Disposition value or with improper Content-Length header. If the Content-Disposition/Content-Length value is not valid an exception is thrown which is then used to display an error message to a user. This is a different vector for the same vulnerability described in S2-045 (CVE-2017-5638).

From this description, we can know that the RCE vulnerability stems from Struts 2’s improper handling of error messages, which is the same as S2-045. The vector, however, is different. S2-046 is triggered via the filename field (containing %00) in Content-Disposition or the Content-Length field (set to a value greater than 2 GB).

Our analysis is based on Struts 2.3.24. Following is a detailed description of the proof of concept (PoC) and verification process.

First, we tried the Content-Disposition header. An attack directive was passed to a vulnerable server via the filename field of Content-Disposition. Then we explored the RCE injection point, which is the same as S2-045.

An observation of the call stack exported as a result of an error revealed something:

We finally pinned down the location of the exception, which turned out to be the checkFileName function. Next we tried to find out how this function works.

First, it checks whether a file name is null. When the file name contains “\u0000”, an error is thrown, containing the filename field. From our PoC, you can see that this field is the one we intended to manipulate. Subsequently, this error message was passed to buildErrorMessage for execution. Like we did for S2-045, we turned our eyes first to doFilter, an entrance to Struts 2.

Then we took a further look at org.apache.struts2.dispatcher.Dispatcher.wrapRequest:

Then we arrived at dispatcher.wrapRequest, which calls org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper for initialization in MultiPartRequestWrapper. This is the so-called adapter design pattern. The initialization requires a call of org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest.parse, which processes uploaded files and will trigger buildErrorMessage in the case of an exception.

Now we came to processUpload, which is implemented as follows:

One step further to processFileField, which processes headers:

The getName function appeared and we did not stop:

From the comment given in the preceding figure, we find that the code pays particular attention to NULL characters. Our further exploration found that this is where the filename field is validated. If a file name contains “\u0000”, an exception is thrown.

localizedTextUtil.findText in the buildErrorMessage function in JakartaMultiPartRequest.java executes OGNL expressions, leading to command execution.

findText is defined as follows:

https://struts.apache.org/maven/struts2-core/apidocs/com/opensymphony/xwork2/util/LocalizedTextUtil.html

findText is then called by the parse function within JakartaMultiPartRequest.java.

Alternatively, in JakartaStream upload mode, namely <constant name=”struts.multipart.parser” value=”jakarta-stream” /> in the Struts 2 configuration file, when Content-Length has a value greater than the maximum (2 GB) permitted by Struts 2, an exception is thrown. The related error message containing the filename field will be dropped into an OGNL expression for execution. The PoC is as follows:

The following figure shows the cause of the vulnerability:

As shown in the preceding figure, the code first determines the size of the requested files. When the size exceeds the maximum value (2 GB) permitted by Struts 2, it calls the addFileSkeppedError function, which, in turn, calls buildErrorMessage, leading to RCE via an OGNL expression.

(3) Fixes and Patches

Apache Struts 2.5.10.1, which has fixed this vulnerability, is available at the following link:

https://github.com/apache/struts/commit/b06dd50af2a3319dd896bf5c2f4972d2b772cf2b

Apache Struts 2.3.32, which has fixed this vulnerability, is available at the following link:

https://github.com/apache/struts/commit/352306493971e7d5a756d61780d57a76eb1f519a

Vendor Solutions

  • According to the official statement, users who are using a Jakarta-based file upload plug-in are advised to upgrade Struts to 2.3.32 or 2.5.10.1.

Struts Upgrade Guide:

In 2016, the Struts 2 Remote Code Execution Vulnerability (S2-032) also caused catastrophes to numerous application systems. To ensure the versatility of the system, we use Struts 2.3.32, which is not affected by this vulnerability, as an example to show how to upgrade Struts 2.3.24 (the first official release after the disclosure of the S2-032 vulnerability) to an invulnerable version.

First, download the upgrade package of Struts 2.3.32 from the following link:

http://struts.apache.org/download.cgi

Comparing Struts 2.3.24 with 2.3.32, we find that some dependencies are also updated besides the cores (struts2-core and xwork-core).

For the upgrade of Struts 2, we need to replace the associated JAR packages with the latest Struts 2 packages, three of which must be installed:

  • struts2-core-2.3.32.jar: Struts 2 core package, where the S2-046 vulnerability exists.
  • xwork-core-2.3.32.jar: Struts 2 core package, updated with Struts 2.
  • ognl-3.0.19.jar: used to support OGNL expressions and providing dependency for other packages.

Take struts2-blank (an official sample of Struts 2) as an example. First, back up the original dependency libraries for the possible need of restoration upon an upgrade failure, to avoid any impact on the service.

Replace key files:

Start Tomcat and test whether the service is properly running:

  • If it is impossible to upgrade Struts 2 immediately, users can download two latest Jakarta plug-in versions officially released for emergency use from the following link:

https://github.com/apache/struts-extras

Copy the JAR package to the WEB-INF/lib directory and then restart the application.

  • Use other Multipart parsers such as Pell and Cos. The procedure is as follows:

Take Cos as an example. Modify the struts.xml file in the WEB-INF/classes directory by adding <constant name=”struts.multipart.parser” value=”cos” /> under the struts node.

Note: Modifying the file parser will affect the file upload function. Therefore, after modification, users need to adjust related settings.

Technical Solutions

Products

  • If you are not sure whether your Apache Struts is affected by this vulnerability, do as follows:
  1. For Internet-facing assets, use the emergency vulnerability detection service of NSFOCUS Cloud to check for the vulnerability online. The service is available at the following link:

https://cloud.nsfocus.com/#/krosa/views/initcdr/productandservice?page_id=12

  1. For internal assets, use NSFOCUS RSAS V5/V6 or WVSS to check for the vulnerability:

Remote Security Assessment System (RSAS V5):

http://update.nsfocus.com/update/listAurora/v/5

Remote Security Assessment System (RSAS V6):

http://update.nsfocus.com/update/listRsasDetail/v/vulweb

Web Vulnerability Scanning System (WVSS):

http://update.nsfocus.com/update/listWvss

You should upgrade your devices to the latest version by downloading upgrade packages from the preceding links before using them to detect vulnerabilities.

  • Use NSFOCUS’s protection product (NIPS, NIDS, NF, or WAF) to protect against the exploitation of the vulnerability:

Network Intrusion Prevention System (NIPS):

http://update.nsfocus.com/update/listIps

Network Intrusion Detection System (NIDS):

http://update.nsfocus.com/update/listIds

Next-Generation Firewall (NF):

http://update.nsfocus.com/update/listNf

Web Application Firewall (WAF):

http://update.nsfocus.com/update/wafIndex

You should upgrade your devices to the latest version by downloading upgrade packages from the preceding links before using them for protection.

Services:

NSFOCUS provides professional security techniques and services, protecting customers’ application systems in an all-round manner from the possible impact of this vulnerability.

  • Short-term service: This type of service is provided in case of emergency, covering remediation suggestions specific to customers’ application systems and assurance of a secure upgrade.
  • Medium- to long-term service: Revolving around its detection and protection products, NSFOCUS offers security operations services 24/7, including notifying customers immediately after learning a security threat to their application systems, conducting regular security checks, and delivering professional solutions to mitigate specific security risks.

Workaround

It is NSFOCUS’s utmost concern to help ensure customers’ business continuity. Therefore, to avoid the possible impact of Struts 2 upgrading on the service, NSFOCUS provides a professional code-level protection solution to secure customers’ systems.

Our analysis finds that the S2-046 vulnerability allows attackers to cause remote code execution by adding NULL pointer characters in the filename field of Content-Disposition or setting an overlarge Content-Length value (greater than 2 GB), which could trigger an exception. We can detect this vulnerability by checking the Content-Length value and the validity of the file name.

Add a filter to capture attack requests before they reach StrutsPrepareAndExecuteFilter of Struts 2. The filter will check the Content-Length and Content-Type values and allow packets that meet the follow conditions to pass:

  1. The request content does not exceed 2 MB.
  2. Content-Type has a valid value, impervious to the S2-045 vulnerability.

Check the request content for the uploaded file name to see whether the request contains the vector of the S2-046 vulnerability:

  1. Whether the requested content length exceeds 2 GB.
  2. Whether the file name contains exploitable characters of the S2-046 vulnerability.

Add the filter to web.xml.

Because the filter in question is inherited from org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter, the original Struts filter can be directly replaced with it.

This is how the workaround operates. To completely fix the vulnerability, users should upgrade their Struts 2 to an officially released version not affected by this vulnerability.

The related code is as follows:

Struts2Filter.java:

package com.strutsfilter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter;

public class Struts2Filter extends StrutsPrepareAndExecuteFilter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String contentType = null;
        int contentLength = request.getContentLength();
        ServletContext sctx = request.getServletContext();
        String params = sctx.getInitParameter("content-type-param");
        if (request.getContentType() != null) {
            contentType = request.getContentType().toLowerCase(Locale.ENGLISH);
            // Allows requests that are not for file upload and are smaller than 2 MB to pass.
            if (params.contains(contentType) && contentLength < 2097152) {
                super.doFilter(request, response, chain);
            }
        }
        contentType = contentType.contains(",") ? contentType.split(",")[0].trim() : contentType.split(";")[0].trim(); // Trims files for file upload requests.
        if (contentType != null && contentLength < 2097152000) { // Checks requests for upload of files smaller than 2 GB.
            if (!Contain_space(request)) { // Checks whether the content type is included in the whitelist and the names of uploaded files do not contain NULL characters.
                super.doFilter(request, response, chain);
            } else {
                PrintWriter writer = response.getWriter();
                writer.write("reject!");
                writer.flush();
                writer.close();
            }
        }
    }

    public boolean Contain_space(ServletRequest request) {
        try {
            InputStream is = request.getInputStream();
            BufferedReader read = new BufferedReader(new InputStreamReader(is, "utf-8"));
            StringBuilder sb = new StringBuilder();
            String tmp = null;
            while ((tmp = read.readLine()) != null) {
                sb.append(tmp + "\r\n");
            }
            Pattern pattern = Pattern.compile("filename(.*?)\r\n");
            // Checks the filename field (to the next carriage return) and identify file names based on regular expressions.
            Matcher matcher = pattern.matcher(sb.toString().toLowerCase(Locale.ENGLISH)); // Writes request content with lowercase letters.
            while (matcher.find()) {
                String filename = matcher.group();
                if (filename.contains("\\0b") || filename.contains(" ") || filename.contains("\\u0000")
                        || filename.contains("@ognl")) { // Checks file names and filters out upload requests for file names that contain NULL characters.
                    return true;
                }
            }
        } catch (IOException e) {
            //e.printStackTrace();
        }
        return false;
    }

}

Reference configuration for web.xml:

<web-app>
  <display-name>Struts 2 Web Application</display-name>

  <filter>
    <filter-name>struts2</filter-name>
      <filter-class>com.strutsfilter.Struts2Filter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <context-param>
    <param-name>content-type-param</param-name>
    <param-value>application/octet-stream,application/pdf,application/vnd.android.package-archive,
    application/vnd.rn-realmedia-vbr,application/x-bmp,application/x-img,application/x-javascript,
    application/x-jpe,application/x-jpg,application/x-png,application/x-shockwave-flash,
    application/x-x509-ca-cert,application/x-xls,audio/mp3,image/gif,image/jpeg,image/png,
    image/x-icon,image/rfc822,text/css,text/html,text/plain,text/xml,video/mpg,video/mpeg4,video/mpg,
    video/x-ms-wmv,application/x-www-form-urlencoded,multipart/form-data</param-value>
  </context-param>
  
</web-app>

Statement

This advisory is only used to describe a potential risk. NSFOCUS does not provide any commitment or promise on this advisory. NSFOCUS and the author will not bear any liability for any direct and/or indirect consequences and losses caused by transmitting and/or using this advisory. NSFOCUS reserves all the rights to modify and interpret this advisory. Please include this statement paragraph when reproducing or transferring this advisory. Do not modify this advisory, add/delete any information to/from it, or use this advisory for commercial purposes without permission from NSFOCUS.

About NSFOCUS

NSFOCUS IB is a wholly owned subsidiary of NSFOCUS, an enterprise application and network security provider, with operations in the Americas, Europe, the Middle East, Southeast Asia and Japan. NSFOCUS IB has a proven track record of combatting the increasingly complex cyber threat landscape through the construction and implementation of multi-layered defense systems. The company’s Intelligent Hybrid Security strategy utilizes both cloud and on-premises security platforms, built on a foundation of real-time global threat intelligence, to provide unified, multi-layer protection from advanced cyber threats.

For more information about NSFOCUS, please visit:

http://www.nsfocusglobal.com.

NSFOCUS, NSFOCUS IB, and NSFOCUS, INC. are trademarks or registered trademarks of NSFOCUS, Inc. All other names and trademarks are property of their respective firms.

Spread the word. Share this post!

Meet The Author

Leave Comment