Symantec Endpoint Protection Manager XXE/SQLi: From Disclosure To PoC

By Fortra's Digital Defense

FINDING CVE-2013-5014 AND CVE-2013-5015

Sometimes there is nothing more ironic than coming across critical vulnerabilities in the very security software designed to protect systems.  In these cases not only does the security software fail to prevent an intrusion; it actually becomes the vector that allows system compromise of an otherwise secure machine.  Several antivirus products have had these sorts of issues over the years and recently an important one surfaced in the Symantec Endpoint Protection Manager server.  So here's what we know from the initial advisory from Stefan Viehböck:

There are basically two bugs involved here. The first is an XML external entity injection bug, and the second is a SQL injection bug. At first glance, nothing seems too extraordinary here. The beauty in all of this is being able to leverage the two vulnerabilities in a single exploit that will allow you to run any command remotely.

 

To begin searching for the XXE, the first logical step is to think about how it would be triggered. Since exploiting an XXE relies on XML data being processed by a parser, the most obvious place to look would be somewhere that processes HTTP POST data. The advisory stated that the vulnerability existed in ConsoleServlet, so let's have a look there. Symantec stores all of the Tomcat jar files in "Program FilesSymantecSymantec Endpoint Protection ManagerTomcatwebappsROOTWEB-INFlib". ConsoleServlet is located in the scm-server.jar under com.sygate.scm.server.servlet.

 

file path directions

file extraction

 

Before  looking in ConsoleServlet, it can be helpful to get an idea of what sort of HTTP requests the Endpoint Protection Manager client sends to the web server. Unfortunately, the client will only communicate with the server over port 8443, which uses SSL. Thankfully, Symantec leaves a backup copy of the SSL private key in "Program FilesSymantecSymantec Endpoint Protection Manager Server Private Key Backup". In previous versions you had to extract the key from a .jks file, but later versions leave the actual private key under the Apache folder in that directory. Once you have the key, you can fire up Wireshark and add the key so you can decrypt the HTTPS requests on port 8443. Be aware that you will need to modify the list of ciphers that Tomcat allows in the server.xml file. If not, the client will end up using Diffie Hellman with an ephemeral key and you won't be able to decrypt the SSL traffic. After Wireshark is set up, launch the client and log in to the interface.

 

When I was trying to find the XXE I specifically isolated requests to POST. After navigating through the client for a few minutes I had enough HTTP requests to start looking for some interesting data. The first thing you will notice in all of the HTTP requests is that they involve either POST or GET to "/servlet/ConsoleServlet?ActionType=". The ActionType parameter is  used to select a specific http handler for the request. For example, the first POST that caught my eye was to /servlet/ConsoleServlet?ActionType=ConsoleLog. If you take a look in the scm-server.jar file under com.sygate.scm.server.consolemanager, there is a RequestHandler class that is used to deal with what comes in via the ActionType parameter. If you decompile the RequestHandler.class file, under the public RequestHandler method you can see some initial checking is performed before the call to handleRequest():

 

 if(!RequestValidator.validateParameters(paramRequestData))   
 {  
      this.root.setattribute("ResponseCode", "302776354");  
      return;   
 }   
 handleRequest();  

SEPM typically responds to requests with an XML response that contains a ResponseCode. You will see several ResponseCodes being set throughout the RequestHandler and you can see them returned when you send a malformed or un-authenticated request to the ConsoleServlet:

 <?xml version="1.0" encoding="UTF-8"?>  
 <Response ResponseCode="285278215"/>  

If you take a look at the handleRequest method in the RequestHandler you can begin to understand how SEPM processes requests. Specifically, it makes sure the requestData that comes in is not null, that the actionType parameter was not null, and finally that the actionType is a valid one. If you pass all of these conditions, there are several opportunities for SEPM to handle your un-authenticated request before rejecting it completely due to lack of admin credentials. The four requests (at least on version 12.1) that you can send without auth are ResetPassword, Login, ConfigServer, and LicenseStatus. Each of these requests are handled by an associated handler class under com.sygate.scm.server.consolemanager.requesthandler.

sepm_requesthandler_vuln(3)

My initial instinct was that I would find the XXE in one of these request handlers. Unfortunately, I quickly learned that you cannot send data to most of these without being authenticated. Nevertheless, it turned out that the ConfigServerHandler.class was where the SQL injection was, but we will get to that later.
After looking around in some of the request handlers, I ended up going back to inspect the ConsoleServlet.class file. This time I took a closer look at the following code:

 if ((str2 != null) && (str2.toLowerCase().startsWith("multipart/")))   
 {     
      localObject1 = new MultipartParser();     
      localObject2 = ((MultipartParser)localObject1).parse(paramHttpServletRequest);  
      localRequestData = new RequestData(paramHttpServletRequest, paramHttpServletResponse);     
      localRequestData.putAll((Map)localObject2);   
 }  

sepm_consoleservlet_vuln(4)

 

So when a request comes in to the ConsoleServlet, it checks the content type and if it's a multipart message type, a custom MultipartParser is used to parse the message. The MultipartParser class is located in scm-server.jar under com.sygate.scm.server.util. If you decompile this class you will find the XXE issue in the parseBody method:

 Object localObject;   
 if("text/xml".equalsIgnoreCase(paramMimeBodyPart.getContentType()))   
 {     
      try  
      {       
           DocumentBuilderFactory localDocumentBuilderFactory = DocumentBuilderFactory.newInstance();       
           localObject = localDocumentBuilderFactory.newDocumentBuilder();       
           Document localDocument = ((DocumentBuilder)localObject).parse(localInputStream);  
           paramHashtable.put(str1, localDocument);     
      }     
      catch (Exception localException) {}   
 }  

sepm_multipart_vuln(5)

If the body of the multipart message contains a Content-Type set to text/xml, SEPM will use the javax.xml.parsers.DocumentBuilder class to parse the XML. By default, the XML parser will process declared DTDs within the XML request and you can then trigger the XXE.
So now that we know where the XXE vulnerability is, what to do with it? From my investigation, the XXE injection appeared to be blind. While there are a number of ways to still get data from a blind XXE, none of these attempts were successful in my tests. Instead, I decided to focus on the SQL injection in ConfigServerHandler.class.
ConfigServerHandler.class is located in the scm-server.jar file under com.sygate.scm.server.consolemanager.requesthandler. Upon decompiling this class, you will notice at the beginning of the handleRequest method there is this block of code (version 12.1):

 boolean bool = false;  
 try  
 {  
      bool = Utility.isLoopbackAddress(paramRequestData.getRemoteIP());  
 }  
 catch (UnknownHostException localUnknownHostException)  
 {  
      ServerLogger.log(localUnknownHostException);  
 }  
 if (!bool) {  
      return;  
 }  

sepm_configserver_loopback(6)

This is designed to prevent any request that doesn't come from SEPM itself. Essentially, if the http request didn't come from localhost, ignore the request. Since we now have an XXE injection, we can use this vulnerability to send ConfigServer requests via Server-Side Request Forgery.

After sifting through ConfigServerHandler, I finally came upon a function called updateReportingVersion which accepts two strings as parameters. The second string it accepts is later used in the function to construct a SQL query:

 localDbHelper = DbHelper.getInstance();  
 localObject1 = localDbHelper.getConnection();  
 localStatement = ((Connection)localObject1).createStatement();  
 localObject2 = "select VALUE from GUIPARMS with (NOLOCK) where PARAMETER = '" + paramString2 + "'";  
 localResultSet = localStatement.executeQuery((String)localObject2);  

sepm_configserver_updatereportingversion(7)

This is a pretty glaring vulnerability that also happens to be the textbook case of how SQL injection occurs and why you should never blindly place uncontrolled input into a SQL query.

Now, as luck would have it, the embedded database that is used by SEPM is SQL Anywhere and SQL Anywhere is very much like MSSQL. You can chain queries together using a semi-colon as well as comment out the rest of a query with '--'. In addition, it has an xp_cmdshell stored procedure which lets you run native OS commands. From here it was easy to construct a URL that would trigger the SQL injection. If the request comes from localhost, you can use:

GET /servlet/ConsoleServlet?ActionType=ConfigServer&action=test_av&SequenceNum=140320121&Parameter=a'; call xp_cmdshell('<cmd here>');--

The action "test_av" is what allows you to hit the updateReportingVersion function as seen here:

sepm_configserver_testav(8)

>So there you have it. Without the SQL injection, the XXE appears to be of limited use. Without the XXE, you can't trigger the SQL injection. That's what made this such a good discovery.

Share This