CVE-2019-0230: Apache Struts OGNL Remote Code Execution

October 07, 2020 | Trend Micro Research Team

In this excerpt of a Trend Micro Vulnerability Research Service vulnerability report, Kc Udonsi and John Simpson of the Trend Micro Research Team detail a recent code execution vulnerability in the Apache Struts framework. The bug was originally discovered and reported by Matthias Kaiser of Apple Information Security. The following is a portion of their write-up covering CVE-2019-0230, with a few minimal modifications.


A remote code execution has been reported in the Apache Struts framework. The vulnerability is due to insufficient input validation leading to a forced double Object Graph Navigation Library (OGNL) evaluation when evaluating raw user input. An attacker could exploit this vulnerability by sending a crafted request to the target server. Successful exploitation will allow an attacker to execute arbitrary code with the privileges of the server. 

The Vulnerability

Apache Struts is a Model-View-Controller (MVC) framework for building Java-based web applications. An MVC framework is used to separate the representation of information from the user's interaction with it. The Model consists of business and application logic responsible for back-end work such as communicating with the database. A View is a representation of the data provided to the user, while a Controller is responsible for mediating communication between the Model and the View.

Apache Struts uses the Java Servlet API to provide a Controller called ActionServlet. Requests from a client are sent to the Controller in the form of "Actions" defined in an XML configuration file. The controller invokes the corresponding Action class in the Model code which uses internal logic to validate and perform the action, returning the result in the form of a new View, which the Controller is responsible for updating. Apache Struts allows Views to be coded in Java ServerPages (JSP) that are compiled into HTML pages for viewing on a browser.

Actions are accessed using URIs of the form:

         http://server:port/path/<action name>.action

where <action name> is replaced with an action name. The suffix “.action” is only a convention. A web application based on Struts may use any suffix, such as “.do”.

HTTP is a request/response protocol described in RFCs 7230 - 7237 and other RFCs. A request is sent by a client to a server, which in turn sends a response back to the client. An HTTP request consists of a request line, various headers, an empty line, and an optional message body:

where CRLF represents the new line sequence Carriage Return (CR) followed by Line Feed (LF) and SP represents a space character. Parameters can be passed from the client to the server as name-value pairs in either the Request-URI or in the message-body, depending on the Method used and Content-Type header. For example, a simple HTTP request passing a parameter named “param” with value “1”, using the GET method might look like this:

A corresponding HTTP request using the POST method might look like: 

If there is more than one parameter/value pair, they are encoded as &-delimited name=value pairs:

          var1=value1&var2=value2...

Apache Struts supports the use of Object Graph Navigational Language (OGNL) expressions to dynamically generate web page content from template .jsp files. OGNL is a dynamic Expression Language (EL) with terse syntax for getting and setting properties of Java objects, list projections, lambda expressions, etc. OGNL expressions contain strings combined to form a navigation chain. The strings can be property names, method calls, array indices, and so on. OGNL expressions are evaluated against the initial, or root context object supplied to the evaluator in the form of OGNL Context.

Apache Struts uses a thread local container object called ActionContext to store objects needed to execute an Action. These objects include session identifiers, request parameters, locale, etc. ActionContext also exposes a ValueStack interface used to push and store objects against which dynamic Expression Languages (EL) are evaluated. When the EL compiler needs to resolve an expression, it searches down the stack starting with the latest object pushed into it. OGNL is the primary EL used by Struts and it has the following special syntax:

        -- objectName: object in the ValueStack (default/root object in the OGNL context), such as an Action property.
        -- #objectName: object in the ActionContext but outside of the ValueStack. e.g. #parameters.objectName for request parameters, #session.objectName for session scoped attribute, and so on.
        -- %{ognlexp}: Force OGNL evaluation of an attribute that would normally be a string.
        -- @full.class.name@Static_Field: Used to refer to static fields (variables and methods) of a class.

Of relevance to this report is the ability to force OGNL evaluation of an attribute that would normally be a string using the syntax %{ognlexp}. This syntax is known as alt syntax and is enabled by default. If the ognlexp is obtained from user input without any sanitization, a double evaluation is likely to occur. This situation is especially likely if a forced evaluation is used in tag attributes. For example (using an anchor tag)

         <s:a id="%{fileName}" href="%{url}">Your File</s:a>

If fileName is provided by the user such that a raw OGNL expression gets passed without further validation, the smuggled OGNL expression will be evaluated when the tag is rendered as a result of the request. That is, if fileName is an OGNL expression such as %{2+2} then the rendered tag is the following:

         <s:a id="4" href="%{url}">Your File</s:a>

Upon rendering a response, the doStartTag() method defined in org.apache.struts2.views.jsp.ComponentTagSupport class is called for every tag to be rendered. This method then calls populateParams(). The actual version of this method called, depends on the tag being processed. The appropriate method for the anchor tag is defined as populateParams() in org.apache.struts2.views.jsp.ui.AnchorTag. The first evaluation of the id attribute occurs in the base implementation of populateParams() in a call to setId() defined in org.apache.struts2.components.UIBean class.

After the call to populateParams() the doStartTag() method proceeds to start evaluating the tags and rendering the template. In the start() method defined in org.apache.struts2.components.ClosingUIBean the function evaluateParams() is executed. The evaluateParams() method leverages the populateComponentHtmlId() function to populate the id attribute of the tag. The version of this method called, depends on the tag being rendered. For an anchor tag, the populateComponentHtmlId() defined in org.apache.struts2.components.UIBean is called. This implementation will process the id attribute as an OGNL expression again in the findStringIfAltSyntax() function.

A remote code execution vulnerability exists in the Apache Struts 2 framework. The vulnerability is due to the insufficient restriction of classes and packages available to OGNL expressions, specifically the java.io., java.nio., java.net., sun.misc. packages. While the Apache Struts team has repeatedly identified Double OGNL evaluation as a potential security risk and states that developers should use the forced evaluation feature for processing only trusted data, mitigating controls have been added to minimize the potential for exploitation. These mitigating controls include:

        -- struts.excludedClasses: comma-separated list of excluded classes.
        -- struts.excludedPackageNamePatterns: patterns used to exclude packages based on Regex.
        -- struts.excludedPackageNames: comma-separated list of excluded packages.

In addition, Struts restricts access to Class constructors as well as static methods by default. The SetOgnlUtil() method modifies the SecurityMemberAccess property of the ValueStack to include the exclusions in the above lists. However, if a class or package that is not currently in the exclusions is encountered that permits unsafe behavior, the SecurityMemberAccess.isAccessible() method permits the evaluation of expressions that access or manipulate objects defined in those packages. 

The FreeMarker Java Template Engine used by Apache Struts provides a set of utility classes under the package freemarker.ext.jsp that facilitate a two-way FreeMarker-JSP integration. One of these classes is the TaglibFactory class. This class provides a hash model associated with a servlet context that can load associated JSP tag libraries. The TaglibFactory relies on an instance of the ObjectWrapper class to map Java objects to the type-system of the FreeMarker Template Language. The ObjectWrapper class determines what parts of Java objects will be accessible from templates and how. The TaglibFactory exposes a getter that retrieves the backing ObjectWrapper instance. This ObjectWrapper instance effectively provides a way to create arbitrary Java objects or wrap them if required using the newInstance() and wrap() public methods respectively. An arbitrary class instance with a public constructor can thus be created by first wrapping the arguments using the wrap() method and subsequently, calling newInstance() specifying the class to be instantiated and the wrapped constructor parameters. This gadget effectively nullifies the Struts default restriction on Class constructors. An instance of the TaglibFactory is available to OGNL expressions from the OGNL ValueStack

The Guice lightweight dependency injection container provides a set of utility classes under the package com.opensymphony.xwork2.inject. that provide constructor, method, static method, field, and static field injection. One of these classes is the ContainerBuilder class. This class provides factory methods to build a dependency injection container. More importantly, it provides a public method to create a Container instance. A Container instance holds dependency mappings and provides a public method inject() which can be used to create and inject an arbitrary instance into the dependency mapping. The Container instance effectively provides a way to retrieve instances guarded by static factory methods which will otherwise be unavailable to an OGNL expression. An arbitrary class instance with no public constructor and only a static factory method that returns singleton instances can be obtained by calling inject() and specifying the required class. An instance of the ContainerBuilder class may be obtained using the aforementioned FreeMarker TagLibFactory gadget as it implements a public constructor. 

Given these gadgets, current security controls can be bypassed by obtaining an instance of the restricted class sun.misc.Unsafe, instantiating a class from the java.net. package, opening a connection to and receiving class bytes from a remote machine, and finally defining an arbitrary class using the sun.misc.Unsafe.defineAnonymousClass() method and the bytes received from the remote server. The arbitrary class can then be initialized using the sun.misc.Unsafe.allocateInstance() method. Although the allocateInstance() method does not invoke constructors, other public methods defined for the class can be invoked on the created instance. Alternatively, an instance of a class from the java.io or java.nio packages can be used to create and write arbitrary files, such as JSP files, to arbitrary locations such as the web application root since file path references are available to an OGNL expression from the OGNL ValueStack

A remote attacker can exploit this vulnerability by sending a crafted HTTP request containing a malicious parameter to a vulnerable server. Successful exploitation could result in the execution of arbitrary code with the privileges of the server. 

Conclusion

The Apache Struts team fixed this bug in August 2020 with their S2-059 advisory. According to their write-up, the patch addresses the vulnerability by ensuring proper validation of each value that’s coming in, and by validating it is used in tag’s attributes. They also recommend that you do not use forced evaluation of an attribute other than value using %{...} or ${...} syntax unless needed for a valid use-case. Struts version 2.5.22 and newer versions are not affected by this vulnerability.

Special thanks to Kc Udonsi and John Simpson of the Trend Micro Research Team for providing such a thorough analysis of this vulnerability. For an overview of Trend Micro Research services please visit http://go.trendmicro.com/tis/.

The threat research team will be back with other great vulnerability analysis reports in the future. Until then, follow the ZDI team for the latest in exploit techniques and security patches.