Two weeks ago I published details of an attack method that can be used to bypass various implementations of certificate pinning in Android or generally Java applications.
Several applications and frameworks are still vulnerable to the attack, among them every Java or Android application using a version of the popular OkHttp networking library before versions 3.1.2 and 2.7.4. [The OkHttp issue is tracked as CVE-2016-2402]
Certificate pinning is a control used to mitigate Man-In-The-Middle attacks by privileged attackers. These attackers are assumed to have access to the private key used to sign a certificate that is trusted by the system hosting the application under attack.
It turns out that certificate pinning - if implemented using certain Java APIs like
checkServerTrusted() - without taking extra steps - can be easily bypassed by the same attackers that the control is supposed to protect against. If vulnerable, pinning becomes completely ineffective as a control as it doesn’t do what it sets out to do.
The main reason behind the flaw is that for pinning to work correctly, developers should not check pins against the list of certificates sent by the server. Instead, pins should be checked against the new, ‘clean’ chain that is created during SSL validation.
There has been some confusion over the pre-requirements for such attack. These are:
- The attacker must be able to intercept network comms.
- The attacker must have the private key of a certificate trusted by the host system/device.
Under other circumstances, the 2nd prerequisite would be a ‘huge’ one. However that’s not the case here: Certificate pinning, as a control, only makes sense against attackers that already have such access. Applications that implement pinning want to actively protect against such attackers. The described attack enables this attack vector, bypassing the pinning control.
Local or remote?
Some confusion may also exist over whether this is a ‘local’ or ‘remote’ attack. The answer is that it can be both. It boils down to the method used by an attacker to obtain the private key of a trusted certificate. Some of these are mentioned in the original post:
- Compromising a CA or intermediate CA
- Direct or indirect access to a CA
- 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
If an attacker uses a compromised trusted CA key, the attack can be remote. If an attacker needs to install or somehow trick the user into installing their own malicious CA certificate, then the attack could be local and requires the attacker to ‘phish’ a user.
How to test
Say Alice is the client, Bob is the server and Mallory is the intercepting host.
The general idea is:
- Alice, while attempting to connect to Bob, is somehow redirected to Mallory.
- Mallory knows that Alice wanted to connect to Bob, so, before fully establishing an SSL connection with Alice, he first contacts Bob and grabs all his SSL certificates.
- Mallory then uses the private key of a CA that Alice trusts to sign a certificate masquerading as belonging to Bob.
- Mallory also appends all of Bob’s real certificates in the list of certificates it sends to Alice
- Alice completes the SSL handshake with Mallory instead of Bob. The handshake is verified successfully because Mallory’s certificate is signed by a CA that Alice trusts.
- Alice now goes through certificate pinning checks using a vulnerable implementation which looks for Bob’s exact certificates in the received chain. The checks pass because …they are there.
Here’s a sequence diagram version of the above steps:
If the above scenario is possible, then an application that claims to perform pinning is vulnerable.
Testing for this can be tricky, because no ready-made tools exist - so I created a few.
While submitting bug reports to OkHttp and several other applications I used a simple python script that acts as a malicious HTTPS web server that masquerades as a specified trusted server. You can find it in my github. Start this server with a command line parameter indicating which trusted server to masquerade as (domain name). Any client connecting to it will receive a certificate chain that looks like the following:
 malicious server end-entity cert signed by CA A  real server end-entity cert signed by CA B  real server intermediate CA B signed by CA C
CA A and CA C are trusted by the system.
Of course, for this to work, for POC purposes we have to manually create a ‘CA A’ certificate and insert it in the host system’s trusted store. A real attacker may not have to do this step.
Using this for testing is simple:
- Start the POC server masquerading as a domain name the application is going to connect to
- Import the POC server’s CA into the app’s host system
- Redirect traffic from the application to the server. The easy way is to change
/etc/hostsso that requests for a particular domain end up to the malicious server. DNS spoofing or a variety of other methods can also be used.
- Once you start the application, if the attack worked, you should see GET or POST requests hitting the malicious web server - you should be able to read their contents. The POC server is not a proxy - there will be no responses back or any means for the application to make use of the connection. This is just to prove the attack works. If pinning was not vulnerable, then you wouldn’t see a plaintext GET or POST request in your malicious server’s logs; the client should have refused to establish the SSL channel with the malicious server.
Once you have the code, all you have to do is:
- Insert mitmproxy’s CA certificate in the tested system (unless you have the key of a trusted CA like a real attacker would)
- Configure device networking to pass through mitmproxy (e.g. proxy settings on the device, invisible proxying via vpn, gateway etc)
- Start mitmproxy or mitmdump using the new
--add-upstream-certs-to-client-chaincommand line switch.
While operating in this mode, mitmproxy will automatically add all certificates of the upstream server to the certificate list that is served to the client.
If connections using normal proxying fail (due to pinning) but work in this new mode, then you can easily conclude that the pinning implementation exhibits this flaw.
Sample vulnerable app
I created [a sample vulnerable Java app] for demo purposes. Find it in my github. This uses OkHttp 3.0.1 (which is vulnerable to CVE-2016-2402) to connect to github.com and retrieve a file.
Since pinning is used, if you attempt to proxy this app or redirect its connections you shouldn’t be able to see the encrypted traffic as the application will refuse to connect to the proxy - even if the proxy certificate is trusted by the system.
However, because OkHttp 3.0.1 is vulnerable to CVE-2016-2402, you should be able to intercept the traffic if you redirect to the POC malicious server or if you use mitmproxy with the new