Normal view

There are new articles available, click to refresh the page.
Before yesterdayWeb App

Ping'ing XMLSec

By: An Trinh
30 September 2021 at 19:29

Apache Santuario, commonly known as Apache XML Security, is a widely used library to handle XML Digital Signature and XML Encryption. It's also one of the few external libraries bundled in the JDK under the repackaged com.sun namespace. This post details a form of attack on the library and showcases how it could lead to heavy information leak on one of the popular Single Sign On products relying on it, PingFederate.

An attack vector on Santuario

XML Digital Signature is documented in the W3C xmldsig specs [1]. A special feature was described in section the xmldsig processing application is expected to dereference the uri in http scheme, or in other words to invoke http requests from it. Santuario implements the mechanism under ResolverDirectHTTP, but what's more interesting is that it resolves file uri scheme as well under ResolverLocalFilesystem.

At first thought it seems this mechanism could only happen in a Reference element which has the limitation that the codepath is only reachable after a valid Signature check. That in turn requires one to have a trusted private key to forge and sign the message, restricting the scenario to (kind of) post-auth. Specified at section 4.5.3 and 4.5.10, it turns out there are two more elements KeyInfoReference and RetrievalMethod that both use the same dereference mechanism. As part of the KeyInfo operation, they are expected to be handled before any Signature check and thus can lead to a pre-auth exploit. With this, one can embed any local file resource inside the XML DOM structure, however there's still no way yet to extract its contents.

Another special feature taking place after resource dereferencing is the Transform element, which does what its name says. There are several possible transforms but XPath and XSLT immediately stick out. With those at hands, an idea for the exploit is to construct an XPath that isolates a specific part of the target XML node, forms a conditional test on it, then construct computationally intensive XSLT queries that take up heavy processing time inside one of the two conditional branches. With the proper timing this allows one to determine whether his XPath query is True or False and consequently form an oracle. This can be exploited to leak every XML node's contents, especially in the referenced local xml file, one part at a time.

The Transform in which case would look like:

<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds:RetrievalMethod URI="file:/some/important/secret.xml">
      <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">
        <xsl:stylesheet version="1.0" xmlns:foo="http://foo" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
          <xsl:template match="foo:bar">
          <xsl:if test="substring(./@pass,1,3)='sec'"> (1)
            <xsl:for-each select="//.">
            <xsl:for-each select="//.">
            <xsl:for-each select="//.">
            <xsl:for-each select="//.">
            <xsl:for-each select="//.">

Assuming the target to extract is <foo:bar pass="secret"/>, the XPath at (1) tests whether the attribute value starts with 'sec' while the inner XSLT code, which was optimized from an XSLT Denial of Service payload, is meant to take about 4 seconds to process, forming quite a reliable timing oracle. Another idea for an XPath oracle is to construct an adjacent element that has an HTTP resource reference operation such that only when the XPath query satisfies can the HTTP request fire.

The biggest limitation with this is by using either XPath or XSLT, the referenced local file is required to be valid xml data. However that could already be critical when apps store their secrets in xml files.

Santuario secureValidation bypass

Santuario implements a defense in depth property named secureValidation and with that turned on, it refuses to load ResolverDirectHTTP or ResolverLocalFilesystem. However during handling of a KeyInfoReference element, the secureValidation property is not properly passed down to the new object so it always bears the default setting, which is off. The bug was assigned CVE-2021-40690. Although simple, with this bypass there isn't any mechanism in Santuario to protect against the above attack. Unfortunately the attack vector and how it could be exploited by default are not highlighted in the advisory from Santuario.

There are at least 3 places in Santuario where this can be exploited: the Reference element, the KeyInfo element and the EncryptedKey element. KeyInfo is usually designed to support embedded certificate and EncryptedKey is part of XML Encryption specs [2], both are considered pre-auth.


PingIdentity's flagship PingFederate is a common product for organizations to implement their SSO solution. It serves as a nice demonstration as their SAML implementation relies heavily on Santuario. Naturally this does not mean it's the only product affected.

Ping went one step further and implemented a custom safeguard at org.sourceid.common.dsig.XmlSignatureVerifier.validateRestrictions() to enforce an allowlist of transform Algorithm a Signature can contain and it does not have XPath. This could be circumvented by using a RetrievalMethod element to reference an external document via http so when the external document is loaded, its contents would not have to go through .validateRestrictions(), a TOCTOU issue. Another bypass that works in PingFederate's versions at least from 10.1 backwards is that when handling an attribute whose name start with '::' such as '::Algorithm', the inconsistency in xml node parsing from Xmlbeans underlying parser Piccolo, PingFederate and Xerces lead to a mismatch in attribute identification.

Leaking files via the XPath oracle could have worked well, however exploiting it is more straightforward in PingFederate. Santuario has a neat type of exception that is only thrown when handling XPath query: XMLSecurityRuntimeException, subclass of Java RuntimeException. What's special about it is that the exception message includes the current XPath node .toString() which always contains the full data that that node represents. And as it's an unchecked exception, it propagates all the way into upper layers of the app. A common behaviour among SAML supported apps, PingFederate included, is that since most SAML bindings are HTTP-based, error messages are usually caught, redirected to the log and a generic error page is returned instead. Except for SOAP-based binding which is special because it's designed to be an API-friendly interface, PingFederate includes additional basic error information in the SOAP response: the message fetched from Exception.getMessage(), which in this case is the unchecked exception received earlier. This can be considered another attack vector in addition to the XPath oracle. It has the same underlying mechanism (in that they both use resource dereferencing and transform), requires more conditions but is easier to exploit.

Following is snippet of a sample exploit via the KeyInfo element. The attribute Id="payload" below needs to be registered in the DOM as the ID attribute, in order for it to be referenced to. To do that, one can either declare a DOCTYPE ATTLIST or rely on the application for a call to org.w3c.dom.Element.setIdAttributeNode().

<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds11:KeyInfoReference URI="#payload" xmlns:ds11="http://www.w3.org/2009/xmldsig11#"/>  (1)
<ds:KeyInfo Id="payload" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds:RetrievalMethod URI="file:/opt/out/instance/server/default/data/pingfederate-admin-user.xml">
      <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" ::Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">  (2)
self::text() and name(parent::node())='adm:hash' )))</ds:XPath>  (3)
      <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>

(1) is used to bypass Santuario secureValidation, (2) to bypass PingFederate Algorithm check and the XPath at (3) is meant to throw an Exception when the current node matches the target node which in this case is when the condition self::text() and name(parent::node())='adm:hash' is met. The resulting SOAP response should contain the hash of PingFederate admin user in the form of an exception. On top of that, XSLT can assist to dump more, or all xml nodes in one go.

Escalating vectors

One thing might be needed to complete the chain. PingFederate puts an encryption layer on top of sensitive plaintext credentials (which could be retrieved from the xml files) with a key called PingFederate MasterKey stored in pf.jwk. This file however can't be extracted via the above attack as it's not an xml. As it happens, there's also an XXE vulnerability in PingFederate with the root cause in XmlBeansUtil.newDomNode() plus the fact XmlBeans and PingFederate not completely sanitizing the doctype declaration. Using it to extract pf.jwk is pretty straightforward via out-of-band methods as the file is a one-line Json Web Key and does not have special characters. That was assigned CVE-2021-41770.

Most PingFederate configurations are stored in xml files, as such a very large part of information about the target SSO portal can be directly obtained via the xml disclosure attack. Other than that, there are several types of credentials we picked up during the research that may assist in direct escalation:

  • Encrypted, reversible secrets for various configurations such as OIDC and custom adapters at adapter-config/*.xml, password-credential-validators/*.xml and bearer-access-token-management-plugins/*.xml
  • Credentials to invoke SCIM api at sourceid-soap-auth.xml (switched in later versions from encrypted secrets to hashes)
  • PingFederate's internal keys at pingfederate-system-keys.xml to forge password reset tokens for local identity profiles

The product itself is quite complex and each organization has its own unique set of configurations. Through evaluating real world targets, the biggest and most demonstrable impact by far is the disclosure of various external management API information and credentials, from which one can pivot to access and manage the company's production SSO data.

Organizations' reaction

Taking into account the size and complexity of the product it's impressive how several companies resolved this in a matter of days. These yielded maximum bounty rewards from Netflix, PayPal, and another big name who wishes to remain undisclosed. Ping also rewarded nicely for the reports.

After the patches were out, we wanted to monitor the Internet and keep track of their patching progress. The trick used here is to check for the Last-Modified header of a static file on the target (idea courtesy of Orange). While it theoretically makes sense considering a typical product update process, it's still not a guaranteed probe so there most likely will be false positives (actual patched status larger than shown).


In addition to the bypass fix, quite a number of changes were made to the Santuario library, essentially hardening it against this attack vector. They are filed under SANTUARIO-572, 573, 574, 575 and 577. Users should upgrade to version 2.2.3 or 2.1.7, but preferably 2.3.0 when it comes out as it incorporates more hardening. As for Ping, they released the patches nearly 2 months ago for all their product versions to address the issues. An advisory is still not yet released, but I was told it's underway.

Finally, props to all parties for their prompt responses, additionally to Ping for an uncommonly well written product that makes a nice challenge.

[1] https://www.w3.org/TR/2013/REC-xmldsig-core1-20130411
[2] https://www.w3.org/TR/2013/REC-xmlenc-core1-20130411/#sec-eg-EncryptedKey

A Saga of Code Executions on Zimbra

By: An Trinh
13 March 2019 at 10:42
Zimbra is well known for its signature email product, Zimbra Collaboration Suite. Putting client-side vulnerabilities aside, Zimbra seems to have very little security history in the past. Its last critical bug was a Local File Disclosure back in 2013.

Recently with several new findings, it has been known that at least one potential Remote Code Execution exists in all versions of Zimbra. Specifically,

- Pre-Auth RCE on Zimbra <8.5.

- Pre-Auth RCE on Zimbra from 8.5 to 8.7.11.

- Auth'd RCE on Zimbra 8.8.11 and below with an additional condition that Zimbra uses Memcached. More on that in the next section.

Breaking Zimbra part 1

1. The XXE cavalry - CVE-2016-9924, CVE-2018-20160, CVE-2019-9670

Zimbra uses a large amount of XML handling for both its internal and external operations. With great XML usage comes great XXE vulnerabilities.

Back in 2016, another researcher discovered CVE-2016-9924 with the bug locating in SoapEngine.chooseFaultProtocolFromBadXml(), which happens on the parsing of invalid XML requests. This code is used in all Zimbra instances version below 8.5. Note however, as there's no way to extract the output to the HTTP response, an out-of-band extraction method is required in exploiting it.

For more recent versions, CVE-2019-9670 works flawlessly where the XXE lies in the handling of Autodiscover requests. This can be applied on Zimbra from 8.5 to 8.7.11. And for the sake of completeness, CVE-2018-20160 is an XXE in the handling of XMPP protocol and an additional bug along CVE-2019-9670 is a prevention bypass in the sanitizing of XHTML documents which also leads to XXE, however they both require some additional conditions to trigger. These all allow direct file extraction through response.

It's worth to mention that exploiting out-of-band XXE on recent Java just got a lot harder due to a patch in the core FtpClient which makes it reject all FTP commands containing newline. This doesn't affect the exploits for the vulnerabilities mentioned above, but it did make some of my previous efforts to chain XXE with other bugs in vain.

On installation, Zimbra sets up a global admin for its internal SOAP communications, with the username 'zimbra' and a randomly generated password. These information are always stored in a local file named localconfig.xml. As such, a file-read vulnerability like XXE could potentially be catastrophic to Zimbra, since it allows an attacker to acquire the login information of a user with all the admin rights. This has been demonstrated as the case in a CVE-2013-7091 LFI exploit where under certain conditions, one could use such credentials to gain RCE.

However things have never been that easy. Zimbra manages user privileges via tokens, and it sets up an application model such that an admin token can only be granted to requests coming to the admin port, which by default is 7071. The aforementioned LFI exploit conveniently assumes we already have access to that port. But how often do you see the weirdo 7071 open to public?

2. SSRF to the rescue - CVE-2019-9621

If you can't access the port from public, let the application do it for you. The code at ProxyServlet.doProxy() does exactly what its name says, it proxies a request to another designated location. What's more, this servlet is available on the normal webapp and therefore accessible from public. Sweet! However the code has an additional protection, it checks whether the proxied target matches a set of predefined whitelisted domains. That is, unless the request is from an admin. Sounds right, an admin should be able to do what he wants.

(Un)Fortunately, the admin checks are flawed. First thing it checks is whether the request comes from port 7071. However it uses ServletRequest.getServerPort() to fetch the incoming port. This method returns a tainted input controllable by an attacker, which is the part after ':' in the Host header. What's more, after that the check for the admin token happens only if it is fetched from a parameter, meanwhile we can totally send a token via cookie! In short, if we send a request with 'foo:7071' Host header and a valid token in cookie, we can proxy a request to arbitrary targets that is otherwise only accessible to admins.

3. Pre-Auth RCE from public port

ProxyServlet still needs a valid token though, so how does this fit in a preauth RCE chain? Turns out Zimbra has a 'hidden' feature that can help us generate a normal user token under the special global 'zimbra' account. When we modify an ordinary SOAP AuthRequest which looks like this:
...<account by="name">an</account>...
into this:
...<account by="adminName">zimbra</account>...
Zimbra will then lookup all the admin accounts and proceed to check the password. This is actually quite surprising because Zimbra admins and users naturally reside in two different LDAP branches. A normal AuthRequest should only touch the normal user branch, never the other. If the application wants a token for an admin, it already has port 7071 for that.

Note that while this little trick could give us a token for the 'zimbra' user, this token doesn't have any of the admin flag in it as it's not coming from port 7071. This is when ProxyServlet jumps in, which will help us to proxy another admin AuthRequest to port 7071 and obtain a global admin token.

Now that we've got everything we need. The flow is to read the config file via XXE, generate a low-priv token through a normal AuthRequest, proxy an admin AuthRequest to the local admin port via ProxyServlet and finally, use the global admin token to upload a webshell via the ClientUploader extension.

Breaking Zimbra part 2

Zimbra has its own implementation of IMAP protocol, where it keeps a cache of the recently logged-in mailbox folders so that it doesn't have to load all the metadata from scratch next time. Zimbra serializes a user's mailbox folders to the cache on logging out and deserializes it when the same user logs in again.

It has three ways to maintain a cache: Memcached(network-based input), EhCache(memory-based) and file-based. If one fails, it tries the next in list. Of all of those, we can only hope to manipulate Memcached, and this is the condition of the exploit: Zimbra has to use Memcached as its caching mechanism. Even though Memcached is prioritized over the others, (un)fortunately on a single-server instance, the LDAP key zimbraMemcachedClientServerList isn't auto-populated, so Zimbra wouldn't know where the service is and will fail over to Ehcache. This is probably a bug in Zimbra itself, as Memcached service is up and running by default and that way it wouldn't have any data in it. On a multi-server install however, setting this key is expected as only Memcached can work accross many servers.

To check whether your Zimbra install is vulnerable, invoke this command on everyΒ node in the cluster and check if it returns a value:
$ zmprov gs `zmhostname` zimbraMemcachedClientServerList

This was assigned CVE-2019-6980. The deserialization process happens at ImapMemcachedSerializer.deserialize() and triggers on ImapHandler.doSELECT() i.e. when a user invoking an IMAP SELECT command. The IMAP port in most cases is publicly accessible, so we can safely assume the trigger of this exploit.

To bring this to RCE, one still needs to find a suitable gadget to form a chain. The twist is, none of the current public chains (ysoserial) works on Zimbra.

1. Making of a gadget

Of all the gadgets available, MozillaRhino1 particularly stands out as all classes in the chain are available on Zimbra's classpath. This chain is based on Rhino library version 1.7R2. Zimbra uses the lib yuicompressor version 2.4.2 for js compression, and yuicompressor is bundled with Rhino 1.6R7. The unfortunate thing is there's an internal bug in 1.6R7 that would break the MozillaRhino1 chain before it ever reaches code execution, so we're out of luck. The good thing is, thanks to the effort in attempting to get the original chain to work and to the blog post detailing the MozillaRhino1 chainΒ [1], we learnt a lot about Rhino's internals and on our way to pop another gadget.

There are two main points. First, the class NativeJavaObject on deserialization will store all members of an object's class. Members refer to all elements that define a class such as variables and methods. In Rhino context, it also detects when there's a getter or setter member and if so, it declares and includes the corresponding bean as an additonal member of this class. Second, a call to NativeJavaObject.get() will search those members for a matching bean name and if one is found, invoke that bean's getter. These match the nature of one of the native 'gadget helpers' - TemplatesImpl.getOutputProperties(). Essentially if we can pass in the name 'outputProperties' in NativeJavaObject.get(), Rhino will invoke TemplatesImpl.getOutputProperties() which will eventually lead to the construction of a malicious class from our predefined bytecodes. Searching for a place that we can control the passed-in member name leads to the discovery of JavaAdapter.getObjectFunctionNames() (Thanks to the valuable help from @matthias_kaiser) and it's directly accessible from NativeJavaObject.readObject().

The chain is now available in ysoserial's payload storage under the name MozillaRhino2. It works all the way to the latest version (with some tweaks) and has some additional improvement over MozillaRhino1. One interesting thing I found while reading Matt's blog post is that OpenJDK 1.7.x always bundles with rhino as its scripting engine, which essentially means that these rhino gadgets may very well work natively on OpenJDK7 and below.

This discovery escalates the bug from a Memcached Injection into a Code Execution. To exploit it, query into the Memcached service, pop out any 'zmImap' key, replace its value with the serialized object from ysoserial and next time the corresponding user logins via IMAP, the deserialization will trigger.

2. Smuggling from HTTP to Memcached

RCE from port 11211 sounds fun, but less so practical. So again, we turn to SSRF for help. The idea is to use the HTTP request from SSRF to inject our defined data in Memcached. To accomplish this, first we need to control a field in the HTTP request that allows the injection of newlines (CRLF). This is because a CRLF in Memcached will denote the end of a command and allow us to start a new arbitrary command after that. Second, since we're pushing raw objects into Memcached, our controlled input also needs to be able to carry binary data.

Zimbra has quite a few SSRFs in itself, however there's only one place that suffices both conditions, and it happens to be the all-powerful ProxyServlet earlier.

For a successful smuggle from HTTP to Memcached protocol, you should see something like above under the hood. It has exactly 6 ERROR and 1 STORED, correlating to 6 lines of HTTP headers and our payload, which also means our payload was successfully injected.

3. RCE from public port

That said, things are different when we use SSRF to inject to Memcached. In this situation we could only inject data into the cache, not pop data out because HTTP protocol cannot parse Memcached response. So we have no idea what our targeted Memcached entry's key looks like, and we need to know the exact key to be able replace its value with our malicious payload.

Fortunately, the Memcached key for Zimbra Imap follows a structure that we can construct ourselves. It follows the pattern
- accountId fetched from hex-decoding any login token
- folderNo the constant '2' if we target the user's Inbox folder
- modseq and uidvalidity obtained via IMAP as shown below

Now we have everything we need. Putting it together, the chain would be as follows:
- Get a user credentials
- Construct a Memcached key for that user following the above instructions
- Generate a ysoserial payload from the gadget MozillaRhino2, use it as the Memcached entry value.
- Inject the payload to Memcached via the SSRF. In the end, our payload should look like:
"set zmImap:61e0594d-dda9-4274-87d8-a2912470a35e:2:162:1 2048 3600 <size_of_object>" + "\r\n" + <object> + "\r\n"
- Login again via IMAP. Upon selecting the Inbox folder, the payload will get deserialized, followed by the RCE gadget.

The patches

Zimbra issued quite a number of patches, of which the most important are to fix XXEs and arbitrary deserialization. However the fix is only available for 8.7.11 and 8.8.x. If you happen to use an earlier version of Zimbra, consider upgrading to one of their supported version.

As a workaround, blocking public requests going to '/service/proxy*' would most likely break the RCE chains. Unfortunately there's none that I can think of that could block all the XXEs without also breaking some of Zimbra features.

Edit 30/04: Including a more specific workaround for the Autodiscover XXE and ProxyServlet SSRF which seem to be actively exploited. Locate {zimbra_home}/mailboxd/etc/service.web.xml.in, find the servlet tags named ProxyServlet and AutoDiscoverServlet, remove %%zimbraMailPort%% and %%zimbraMailSSLPort%% in both allowed.ports param and restart Zimbra. This would prevent public access to the affected components. Other than that, this thread provides some useful information suggested by savvy Zimbra user on how to clean an infected instance.

The Burp challenge

30 November 2022 at 09:35
We recently launched the Burp challenge, to give our customers a unique opportunity to demonstrate their skills with Burp Suite Professional. Not only that, but the challenges involved put your web vu

A New Attack Surface on MS Exchange Part 4 - ProxyRelay!

19 October 2022 at 07:58
This is a cross-post blog from DEVCORE. You can check the series on: A New Attack Surface on MS Exchange Part 1 - ProxyLogon! A New Attack Surface on MS Exchange Part 2 - ProxyOracle! A New Attack Surface on MS Exchange Part 3 - ProxyShell! A New Attack Surface on MS Exchange Part 4 - ProxyRelay! Hi, this is a long-time-pending article. We could