Software Integrity

 

An examination of ineffective certificate pinning implementations

While researching certificate pinning, I stumbled upon a ‘generic’ implementation flaw allowing remote attackers to bypass the protection that certificate pinning can offer to an application.

Summary

If your Java or Android application uses the checkServerTrusted() or getPeerCertificates() APIs to implement certificate pinning, there is a very good chance that your pinning implementation is completely ineffective.

The flaw affects a great number of Java and Android applications that have chosen to implement pinning. It also affects several frameworks and libraries that expose a pinning functionality based on those APIs—increasing the number of affected applications even more.

I consider the impact severe for apps that want pinning to work.

This flaw is a classic “API misuse” issue. Developers use APIs expecting them to return something trusted; but the APIs return something that can instead be abused by attackers. These APIs are commonly used to retrieve the server’s certificate and compare it against a list of certificate ‘pins’ the application holds. However, these APIs return the ‘true’ certificate chain of the server which can be directly controlled by an attacker. The certificate chain needs to be ‘cleaned’ before attempting to compare against pins.

There is no need for local access to the device or application for the attack to work. A remote attacker could serve a malicious certificate list to the client and completely bypass the certificate pinning control while performing a Man-in-the-Middle (MitM) attack.

Brief overview of certificate pinning

Certificate pinning is a software control that an application can implement to protect its communications against a special type of MitM attack.

Specifically, it protects application traffic against attackers with the ability to sign certificates using a Certificate Authority (CA) the host system trusts.

These attackers may have gained access to the private key of a trusted CA through several methods:

  • Compromising a CA or intermediate CA. Several such cases were made public in the last few years.
  • Direct or indirect access to a CA. [1][2] For example:
    • A malicious CA employee
    • Phishing a CA employee
    • Leveraging mistakes in the certificate request process
    • Configuration mistake on the CA
    • Nation-state attackers
  • Phishing a user into installing a malicious certificate in their system’s trusted store.

How does certificate pinning help?

If an application uses certificate pinning, it can validate that an expected certificate is included in the validated certificate chain (trusted path) between your server’s certificate and a CA.

If there is no pinning control, malicious actors (defined above) are able to intercept traffic anywhere between the application’s users and the web server.

The implementation flaw

Java/Android applications tend to implement certificate pinning in one of two very different ways:

  1. Replacing the system’s TrustStore with one that only contains specific white-listed certificates. The contents of this TrustStore will be used as the only trust anchors during SSL validation.
  2. Validating the certificate chain (after SSL validation) against ‘pins’ (e.g. hashes) of a certificate that you expect to appear in the trusted path once the SSL handshake is complete.

Both options are very popular. The problem lies in applications or libraries leveraging the second option (SPKI pinning or similar methods).

Here’s the gist of the problem:

Java APIs that retrieve the certificates of a SSL connection are poorly documented, resulting in developers misusing them. To implement pinning, what developers want to retrieve is the “trusted certificate path,” as created by the system’s certificate validation routines. What the APIs return is the list of certificates as sent by a potentially malicious server. These two can be different.

The dark art of chain validation

During the SSL handshake, web servers send a list of several certificates to clients. Usually, chain[0] holds the end-entity certificate, chain[1] holds an intermediate CA certificate, chain[2] holds maybe another intermediate CA certificate, and chain[3] may or may not hold the root CA certificate for that chain.

But, this is not really a chain. It is just a list of certificates that are hints that a client may or may not use while it attempts to form a trusted path between the server’s end-entity certificate and a CA certificate trusted by the system.

It is the client’s job to create and validate this trusted path for each connection, and a server does its best to help by sending certificates the client may need. The server is free to include any certificate it wants in the list of certificates it sends to the client. These could include certificates that may be completely unrelated and do not sign, or are not signed by any relevant CA.

Let’s see an example. Normally, a server would send the following to a client during the SSL handshake:

Certificate Pinning - Certificate Chain

Imagine that an attacker has access to the private key of compromised certificate authority Z.

If the attacker manages to redirect client traffic to a malicious web server, an SSL MitM attack is possible because the system trusts CA Z.

However, if the application makes use of certificate pinning, it could pin to the specific public key of any one of the valid certificates—A, B, C, or D. The application should refuse to connect because these do not appear in the new chain while the MitM attack is happening.

There is nothing stopping an attacker from serving the following malicious chain to a client:

Certificate Pinning - Certificate Chain

In this scenario, a broken chain is presented to the client. The operating system happily validates the connection due to the presence of certificate W (list[0]). It will try to form a valid path between the end-entity certificate W (list[0]) and a CA it trusts, and it trusts CA Z (list[1]). The rest of the certificates are silently ignored by the SSL handshake methods while they construct a new clean chain.

Unfortunately, a developer cannot easily gain access to this new clean chain in standard Java and in past Android versions.

Using the following Java APIs to implement certificate pinning is dangerous:

api1

Using other APIs that just call the previous two under the hood is also dangerous. The following are examples but more may exist:

api2-800

These APIs are going to return the list of certificates as sent by the server. However, what you really want is the list of certificates in the certificate path that the OS validation routine created and trusts. In other words, in order to implement pinning correctly, you need the clean chain.

The problem is clear. Most certificate pinning implementations follow this pattern, where peerCertificates is the output of one of the previously mentioned APIs (example from OkHttp).

for (int i = 0, size = peerCertificates.size(); i < size; i++) {
X509Certificate x509Certificate = peerCertificates.get(i); 
if (pins.contains(sha1(x509Certificate))) return; // Success!
}

 

Each certificate in the server’s list is compared with each of the hardcoded pins. If the list contains any extra certificates (those that are not part of the trusted certificate path), they are checked as well. In the malicious chain scenario, pinning will still succeed because the list received by the server contains the exact certificates the application might pin to.

Mitigation

There are several ways to fix this implementation flaw; however, each mitigation method has its advantages and disadvantages. Mitigation may differ according to each application’s other requirements.

  • The pinning implementation could be restructured so that a custom TrustStore is used, containing only the certificates you trust. This avoids the problem since there is no need to retrieve certificates after the handshake has happened. Instead, a handshake will only succeed if a clean chain ends to specific trust anchors you have configured.
  • The certificate pinning implementation can be limited to only use end-entity certificates as pins. If pinning to CA certificates is not a useful feature for you, the pinning implementation can only check pins against chain[0]. This, by convention, should always hold the actual end-entity certificate used by the server. (Note that this might not be accurate in all cases and I will be testing this assumption further in the future. Also note that this introduces several operational complexities. [3] Maintaining a list of end-entity pins is a complex task.)
  • Leverage Java reflection in order to acquire a handle to TrustManager internals and extract the clean chain used during SSL validation. This method is inherently brittle and requires a lot of testing and deep knowledge of Java networking internals. This method has been used by the OkHttp library.
  • If implementing this on Android, there are a few new APIs that can help, such as: net.http.X509TrustManagerExtensions.checkServerTrusted(). This returns the clean chain, however it is only available on devices running Android 4.2 or newer. Note that Android is actively working towards a cleaner API for applications to use while doing pinning. It will most likely appear in the next major version.

You shouldn’t take lightly an attempt to construct a trusted path yourself, cleaning the chain in the process. X509 chain validation is a complicated process that is best left to SSL network experts. There are a million ways to shoot yourself in the foot in attempting to do this. You’ve been warned.

Affected applications and libraries

As mentioned, this flaw affects several Java and Android applications that implemented certificate pinning via checking received certificates against a list of pins. Some use third-party libraries. Some roll their own pinning. Most are vulnerable. Using those Java APIs to retrieve server certificates is a trap.

I have privately disclosed this information to nine entities so far, including five major e-banking and e-payment applications, popular image sharing applications and others. Four of these entities have resolved the issue already, and the remaining entities are in various stages of the remediation process.

I am certain that many more applications and libraries that contain a broken implementation exist. I advise every application developer to check their implementation.

OkHttp – CVE-2016-2402

OkHttp is one of the libraries affected by this flaw. It is a very popular open-source networking library, maintained by Square, and used by several Java and Android applications. It is also part of the Android Open Source Project—the engine that powers HttpUrlConnection as of Android 4.4.

OkHttp has offered a certificate pinning feature to developers since OkHttp 2.1. Unfortunately, it doesn’t validate pins on a sanitized certificate chain, making it vulnerable to attack. An attacker could exploit this weakness to defeat all protection offered by certificate pinning.

I disclosed this through Square’s responsible disclosure program and it was picked up immediately. I’d like to thank Jesse Wilson and the security team at Square for the streamlined process and their excellent work. The issue was assigned CVE-2016-2402.

This flaw has been resolved in OkHttp 3.1.2 and the fix has been backported to OkHttp 2.7.4. I will leave it to Jesse to describe the pains of fixing this in a sensible way; unfortunately, the APIs offered by Java and Android aren’t great. OkHttp fixed this using reflection as described above. There’s an effort going on to change the relevant Java APIs.

If your app uses OkHttp’s certificate pinning feature, you should upgrade to the latest version of the library immediately.

PhoneGap SSL Certificate Checker

PhoneGap SSL Certificate Checker is a PhoneGap/Cordova plugin used by PhoneGap/Cordova apps to do something like certificate pinning (at least for an initial connection to an endpoint, not all). It is developed by Eddy Verbruggen.

By default, it only conducts end-entity pinning in a safe manner. However, it has a configuration option (checkInCertChain) that allows developers to pin CA certificates as well. If this non-default configuration is used, the pinning implementation becomes insecure and can be bypassed via attack methods discussed above.

The CA-pinning feature has now been disabled and an updated version of the library has been released. I advise everyone to update. I also recommend that the Secure-HTTP Cordova pinning plugin is used instead because its pinning implementation is based on the use of a custom KeyStore.

Is your application vulnerable?

Understanding whether your application is vulnerable to this flaw can be tricky.  For additional details, I created a set of tools and processes to guide you.

Learn more about the strategies designed specifically to address mobile’s unique security challenges.
Footnotes

[1] http://www.h-online.com/security/news/item/Trustwave-issued-a-man-in-the-middle-certificate-1429982.html

[2] https://googleonlinesecurity.blogspot.gr/2015/03/maintaining-digital-certificate-security.html

[3] I will be talking about the complexities involved in designing certificate pinning solutions in the 2016 OWASP AppSecEU conference.