Monday, June 8, 2015 At 7:00AM
While delivering GDS secure SDLC services, we often develop a range of custom security checks and static analysis rules for detecting insecure coding patterns that we find during our source code security reviews. These patterns can represent both common security flaws or unique security weaknesses specific to either the application being assessed, its architecture/design, utilised components, or even the development team itself. These custom rules are often developed to target specific languages and be implemented within a specific static analysis tool depending on what our client is using already or most comfortable with – previous examples include FindBugs, PMD, Visual Studio and of course Fortify SCA.
- Auditing Scala for Insecure Code With Findbugs
- Building Fortify Custom Rules For Spring MVC
- Securing Development with PMD
In this blog post I will be focusing on developing PoC rules for Fortify SCA to target Java based applications, however, the same concepts can easily be extended to other tools and/or development languages.
The recent vulnerability that affected Duo Mobile confirms the analysis of Georgiev et al, who demonstrate a wide range of serious security flaws are the result of an incorrect SSL/TLS certificate validation in various non-browser software, libraries and middleware.
Specifically, in this post we focus on how to identify an insecure use of the SSL/TLS APIs in Java, which could result in Man-in-the-Middle or spoofing attacks allowing a malicious host to impersonate a trusted one. The integration of HP Fortify SCA in the SDLC allows applications to be efficiently scanned for vulnerabilities on a regular basis. We found out that issues occurring due to SSL APIs misuse are not identified with the out of the box rule-sets, thus we developed a comprehensive 12 custom-rule pack for Fortify.
Secure Sockets Layer (SSL/TLS) is the most widely used protocol for secure communication over the web using cryptographic processes to provide authentication, confidentiality, and integrity. To ensure the identity of the party, X.509 certificates must be exchanged and verified. Once the parties are authenticated, the protocol provides an encrypted connection. The algorithms used for encryption in SSL include a secure hash function, which guarantees the integrity of the data.
When using SSL/TLS, the following two steps must be performed in order to ensure no man in the middle tampers with the channel:
- Certificate Chain-Of-Trust verification: a X.509 certificate specifies the name of the certificate authority (CA) that issued the certificate. The server also sends to the client a list of certificates of the intermediate CA all the way to a root CA. The client verifies the signature, expiration (and other checks out of the scope of this post such as revocation, basic constraints, policy constraints, etc) of each certificate starting from the server’s certificate at the bottom going up to the root CA. If the algorithm reaches the last certificate in the chain, with no violations, then verification is successful.
- Hostname Verification: after the chain of trust is established, the client must verify that the subject of the X.509 certificate matches the fully qualified DNS name of the requested server. RFC2818 prescribes to use SubjectAltNames and Common Name for backwards compatibility.
The following mis-use cases can occur when SSL/TLS APIs are not used securely and can cause an application to transmit sensitive information over a compromised SSL/TLS channel.
Trusting All Certificates
The application implements a custom TrustManager so that its logic will trust every presented server certificates without performing the Chain-Of-Trust verification.
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { ... public void checkServerTrusted(X509Certificate[] certs, String authType){} }
This case usually originates from development environments where self-signed Certificates are widely used. In our experiences, we commonly find developers disabling certificate validation altogether instead of loading the certificate into their keystore.This leads to this dangerous coding pattern accidentally making its way into production releases.
When this occurs, it is similar to removing the batteries from a smoke detector: the detector (validation) will still be there, providing a false sense of safety as it will not detect the smoke (un-trusted party). In fact, when a client connects to a server, the validation routine will happily accept any server certificate.
A search on GitHub for the above vulnerable code returns 13,823 results. Also on StackOverflow, a number of questions ask how to ignore certificate errors, obtaining replies similar to the above vulnerable code. It’s concerning that the most voted answers suggest to disable any trust management.
Allowing All Hostnames
The application does not check whether the digital certificate that the server sends is issued to the URL the client is connecting to.
The Java Secure Socket Extension (JSSE) provides two sets of APIs to establish secure communications, a high-level HttpsURLConnection API and a low-level SSLSocket API.
The HttpsURLConnection API performs hostname verification by default, again this can be disabled by overriding the verify() method in the corresponding HostnameVerifier class (there are around 12,800 results when searching for the below code on GitHub).
HostnameVerifier allHostsValid = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } };
The SSLSocket API does not perform hostname verification out of the box. The below code is a Java 8 snippet, hostname verification is performed only if the endpoint identification algorithm is different from an empty String or a NULL value.
private void checkTrusted(X509Certificate[] chain, String authType, SSLEngine engine, boolean isClient) throws CertificateException{ ... String identityAlg = engine.getSSLParameters(). getEndpointIdentificationAlgorithm(); if (identityAlg != null && identityAlg.length() != 0) { checkIdentity(session, chain[0], identityAlg, isClient, getRequestedServerNames(engine)); } ... }
When SSL/TLS clients use the raw SSLSocketFactory instead of the HttpsURLConnection wrapper, the identification algorithm is set to NULL, thus the hostname verification is silently skipped. Thus, if the attacker has a MITM position on the network when a client connects to ‘domain.com’, the application will also accept a valid server certificate issued for ‘some-evil-domain.com’.
This documented behavior is buried in the JSSE reference’s guide:
“When using raw SSLSocket and SSLEngine classes, you should always check the peer’s credentials before sending any data. The SSLSocket and SSLEngine classes do not automatically verify that the host name in a URL matches the host name in the peer’s credentials. An application could be exploited with URL spoofing if the host name is not verified.”
Our contribution: Fortify SCA Rules
To detect the above insecure usage we have coded the following checks in 12 custom rules for HP Fortify SCA. These rules identify issues in code relying on both JSSE and Apache HTTPClient since they are widely used libraries for thick clients and Android apps.
-
Over-Permissive Hostname Verifier: the rule is fired when the code declares a HostnameVerifier, and it always returns ‘true’.
<Predicate> <![CDATA[ Function f: f.name is "verify" and f.enclosingClass.supers contains [Class: name=="javax.net.ssl.HostnameVerifier" ] and f.parameters[0].type.name is "java.lang.String" and f.parameters[1].type.name is "javax.net.ssl.SSLSession" and f.returnType.name is "boolean" and f contains [ReturnStatement r: r.expression.constantValue matches "true"] ]]> </Predicate>
-
Over-Permissive Trust Manager: the rule is fired when the code declares a TrustManager and if it never throws a CertificateException. Throwing the exception is the way the API manages unexpected conditions.
<Predicate> <![CDATA[ Function f: f.name is "checkServerTrusted" and f.parameters[0].type.name is "java.security.cert.X509Certificate" and f.parameters[1].type.name is "java.lang.String" and f.returnType.name is "void" and not f contains [ThrowStatement t: t.expression.type.definition.supers contains [Class: name == "(javax.security.cert.CertificateException|java.security.cert.CertificateException)"]] ]]> </Predicate>
-
Missing Hostname Verification: the rule is fired when the code is using the Low-Level SSLSocket API and does not set a HostnameVerifier.
-
Often Misused: Custom HostnameVerifier: the rule is fired when the code is using the High-Level HttpsURLConnection API and it sets a Custom HostnameVerifier.
-
Often Misused: Custom SSLSocketFactory: the rule is fired when the code is using the High-Level HttpsURLConnection API and it sets a Custom SSLSocketFactory.
We decided to fire the “often misused” rules since the application is using the High-Level API and the overriding of these methods should be manually reviewed.
The rules pack is available on Github. These checks should always be performed during Source Code Analysis to ensure the code is not introducing an insecure SSL/TLS usage.
https://github.com/GDSSecurity/JSSE_Fortify_SCA_Rules
Author: Andrea Scaduto
©Aon plc 2023