-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(java): missing or permissive ssl hostname verifier (CWE-295) (#231)
- Loading branch information
Showing
3 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
imports: | ||
- java_shared_lang_instance | ||
patterns: | ||
- pattern: $<SSL_SOCKET_FACTORY>.ALLOW_ALL_HOSTNAME_VERIFIER; | ||
filters: | ||
- variable: SSL_SOCKET_FACTORY | ||
regex: \A(org\.apache\.http\.conn\.ssl\.)?SSLSocketFactory\z | ||
- pattern: $<CALLER>.$<METHOD>($<...>$<ALLOW_ALL_HOSTNAME_VERIFIER>); | ||
filters: | ||
- either: | ||
- variable: CALLER | ||
detection: ssl_hostname_verifier_socket_factory | ||
- variable: CALLER | ||
detection: ssl_hostname_verifier_https_url_connection | ||
- variable: METHOD | ||
values: | ||
- setHostnameVerifier | ||
- setDefaultHostnameVerifier | ||
- setSSLHostnameVerifier | ||
- either: | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
detection: ssl_hostname_verifier_allow_all_hostname_verifier | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
detection: ssl_hostname_verifier_allow_all_hostname_verifier_instance | ||
- pattern: $<SSL_CONTEXT_GET_INSTANCE>.init($<NULL>, $<_>, $<_>) | ||
filters: | ||
- variable: SSL_CONTEXT_GET_INSTANCE | ||
detection: ssl_hostname_verifier_ssl_context_get_instance | ||
- variable: "NULL" | ||
detection: ssl_hostname_verifier_null | ||
scope: cursor | ||
- pattern: $<TLS_CLIENT_PARAMS>.setDisableCNCheck($<TRUE>); | ||
filters: | ||
- variable: TLS_CLIENT_PARAMS | ||
detection: java_shared_lang_instance | ||
scope: cursor | ||
filters: | ||
- variable: JAVA_SHARED_LANG_INSTANCE_TYPE | ||
regex: \A(org\.apache\.cxf\.configuration\.jsse\.)?TLSClientParameters\z | ||
- variable: "TRUE" | ||
detection: ssl_hostname_verifier_true | ||
- pattern: | | ||
new $<X509_TRUST_MANAGER>() { | ||
$<!>$<...>X509Certificate[] getAcceptedIssuers() { | ||
return $<NULL_X509_CERTIFICATE>; | ||
} | ||
}; | ||
filters: | ||
- variable: X509_TRUST_MANAGER | ||
regex: \A(javax\.net\.ssl\.)?X509TrustManager\z | ||
- variable: NULL_X509_CERTIFICATE | ||
detection: ssl_hostname_verifier_null_x509_cert | ||
- pattern: | | ||
new $<X509_TRUST_MANAGER>() { | ||
$<!>$<...>X509Certificate[] getAcceptedIssuers() { | ||
$<X509_CERTIFICATE_TYPE>[] $<X509_CERT> = $<NULL_X509_CERTIFICATE>; | ||
return $<X509_CERT>; | ||
} | ||
}; | ||
filters: | ||
- variable: X509_TRUST_MANAGER | ||
regex: \A(javax\.net\.ssl\.)?X509TrustManager\z | ||
- variable: X509_CERTIFICATE_TYPE | ||
regex: \A(java\.security\.cert\.)?X509Certificate\z | ||
- variable: NULL_X509_CERTIFICATE | ||
detection: ssl_hostname_verifier_null_x509_cert | ||
- pattern: | | ||
class $<...>$<_> implements $<X509_TRUST_MANAGER> { | ||
$<!>$<...>X509Certificate[] getAcceptedIssuers() { | ||
return $<NULL_X509_CERTIFICATE>; | ||
} | ||
}; | ||
filters: | ||
- variable: X509_TRUST_MANAGER | ||
regex: \A(javax\.net\.ssl\.)?X509TrustManager\z | ||
- variable: NULL_X509_CERTIFICATE | ||
detection: ssl_hostname_verifier_null_x509_cert | ||
- pattern: | | ||
class $<...>$<_> implements $<X509_TRUST_MANAGER> { | ||
$<!>$<...>X509Certificate[] getAcceptedIssuers() { | ||
$<X509_CERTIFICATE_TYPE>[] $<X509_CERT> = $<NULL_X509_CERTIFICATE>; | ||
return $<X509_CERT>; | ||
} | ||
}; | ||
filters: | ||
- variable: X509_TRUST_MANAGER | ||
regex: \A(javax\.net\.ssl\.)?X509TrustManager\z | ||
- variable: X509_CERTIFICATE_TYPE | ||
regex: \A(java\.security\.cert\.)?X509Certificate\z | ||
- variable: NULL_X509_CERTIFICATE | ||
detection: ssl_hostname_verifier_null_x509_cert | ||
- pattern: | | ||
new $<HOSTNAME_VERIFIER>() { | ||
$<!>$<...>$<_> verify($<...>String $<_>, $<...>$<SSL_SESSION> $<_>) { | ||
return $<TRUE>; | ||
} | ||
}; | ||
filters: | ||
- variable: HOSTNAME_VERIFIER | ||
regex: \A(javax\.net\.ssl\.)?HostnameVerifier\z | ||
- variable: SSL_SESSION | ||
regex: \A(javax\.net\.ssl\.)?SSLSession\z | ||
- variable: "TRUE" | ||
detection: ssl_hostname_verifier_true | ||
- pattern: | | ||
class $<...>$<_> implements $<HOSTNAME_VERIFIER> { | ||
$<!>$<...>$<_> verify($<...>String $<_>, $<...>$<SSL_SESSION> $<_>) { | ||
return $<TRUE>; | ||
} | ||
} | ||
filters: | ||
- variable: HOSTNAME_VERIFIER | ||
regex: \A(javax\.net\.ssl\.)?HostnameVerifier\z | ||
- variable: SSL_SESSION | ||
regex: \A(javax\.net\.ssl\.)?SSLSession\z | ||
- variable: "TRUE" | ||
detection: ssl_hostname_verifier_true | ||
auxiliary: | ||
- id: ssl_hostname_verifier_allow_all_hostname_verifier | ||
patterns: | ||
- pattern: $<ALLOW_ALL_HOSTNAME_VERIFIER>; | ||
filters: | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
regex: \A(org\.apache\.http\.conn\.ssl\.)?(AllowAllHostnameVerifier|NoopHostnameVerifier|NullHostnameVerifier)\z | ||
- pattern: $<ALLOW_ALL_HOSTNAME_VERIFIER>.INSTANCE; | ||
filters: | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
regex: \A(org\.apache\.http\.conn\.ssl\.)?(AllowAllHostnameVerifier|NoopHostnameVerifier|NullHostnameVerifier)\z | ||
- pattern: $<SSL_SOCKET_FACTORY>.ALLOW_ALL_HOSTNAME_VERIFIER; | ||
filters: | ||
- variable: SSL_SOCKET_FACTORY | ||
detection: ssl_hostname_verifier_socket_factory | ||
- pattern: $<SSL_SOCKET_FACTORY>.ALLOW_ALL_HOSTNAME_VERIFIER; | ||
filters: | ||
- variable: SSL_SOCKET_FACTORY | ||
regex: \A(org\.apache\.http\.conn\.ssl\.)?SSLSocketFactory\z | ||
- id: ssl_hostname_verifier_allow_all_hostname_verifier_instance | ||
patterns: | ||
- pattern: new $<ALLOW_ALL_HOSTNAME_VERIFIER>(); | ||
filters: | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
detection: ssl_hostname_verifier_allow_all_hostname_verifier | ||
- pattern: ($<HOSTNAME_VERIFIER_CAST>) new $<ALLOW_ALL_HOSTNAME_VERIFIER>(); | ||
filters: | ||
- variable: HOSTNAME_VERIFIER_CAST | ||
values: | ||
- HostnameVerifier | ||
- X509HostnameVerifier | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
detection: ssl_hostname_verifier_allow_all_hostname_verifier | ||
- pattern: ($<HOSTNAME_VERIFIER_CAST>) <$ALLOW_ALL_HOSTNAME_VERIFIER>; | ||
filters: | ||
- variable: ALLOW_ALL_HOSTNAME_VERIFIER | ||
detection: ssl_hostname_verifier_allow_all_hostname_verifier | ||
- variable: HOSTNAME_VERIFIER_CAST | ||
values: | ||
- HostnameVerifier | ||
- X509HostnameVerifier | ||
- id: ssl_hostname_verifier_socket_factory | ||
patterns: | ||
- pattern: $<SSL_SOCKET_FACTORY>; | ||
filters: | ||
- variable: SSL_SOCKET_FACTORY | ||
detection: java_shared_lang_instance | ||
scope: cursor | ||
filters: | ||
- variable: JAVA_SHARED_LANG_INSTANCE_TYPE | ||
regex: \A(org\.apache\.http\.conn\.ssl\.)?SSLSocketFactory\z | ||
- pattern: $<CALLER>.getSocketFactory(); | ||
filters: | ||
- either: | ||
- variable: CALLER | ||
regex: \A(javax\.net\.ssl\.)?SSLContext\z | ||
- variable: CALLER | ||
regex: \A(org\.apache\.http\.conn\.ssl\.)?SSLSocketFactory\z | ||
- id: ssl_hostname_verifier_https_url_connection | ||
patterns: | ||
- pattern: $<HTTPS_URL_CONNECTION>; | ||
filters: | ||
- variable: HTTPS_URL_CONNECTION | ||
regex: \A(javax\.net\.ssl\.)?HttpsURLConnection\z | ||
- id: ssl_hostname_verifier_ssl_context_get_instance | ||
patterns: | ||
- pattern: $<SSL_CONTEXT>.getInstance(); | ||
filters: | ||
- variable: SSL_CONTEXT | ||
regex: \A(javax\.net\.ssl\.)?SSLContext\z | ||
- id: ssl_hostname_verifier_null_x509_cert | ||
patterns: | ||
- pattern: new $<X509_CERT>[]{}; | ||
filters: | ||
- variable: X509_CERT | ||
regex: \A(java\.security\.cert\.)?X509Certificate\z | ||
- pattern: new $<X509_CERT>[0]; | ||
filters: | ||
- variable: X509_CERT | ||
regex: \A(java\.security\.cert\.)?X509Certificate\z | ||
- pattern: $<NULL>; | ||
filters: | ||
- variable: "NULL" | ||
detection: ssl_hostname_verifier_null | ||
- id: ssl_hostname_verifier_null | ||
patterns: | ||
- "null;" | ||
- id: ssl_hostname_verifier_true | ||
patterns: | ||
- "true;" | ||
languages: | ||
- java | ||
metadata: | ||
description: "Missing or permissive SSL hostname verifier" | ||
remediation_message: | | ||
## Description | ||
It is best security practice to always verify the hostname when establishing a SSL/TLS connection. | ||
Failure to do so puts your application at risk of man-in-the-middle attacks. | ||
## Remediations | ||
❌ Do not use `ALLOW_ALL_HOSTNAME_VERIFIER` or similar permissive verifiers | ||
cwe_id: | ||
- 295 | ||
id: java_lang_ssl_hostname_verifier | ||
documentation_url: https://docs.bearer.com/reference/rules/java_lang_ssl_hostname_verifier |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
const { | ||
createNewInvoker, | ||
getEnvironment, | ||
} = require("../../../helper.js") | ||
const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) | ||
|
||
describe(ruleId, () => { | ||
const invoke = createNewInvoker(ruleId, ruleFile, testBase) | ||
|
||
test("java_lang_ssl_hostname_verifier", () => { | ||
const testCase = "main.java" | ||
|
||
const results = invoke(testCase) | ||
|
||
expect(results.Missing).toEqual([]) | ||
expect(results.Extra).toEqual([]) | ||
}) | ||
}) |
131 changes: 131 additions & 0 deletions
131
tests/java/lang/ssl_hostname_verifier/testdata/main.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import javax.net.ssl.X509TrustManager; | ||
import org.apache.http.conn.ssl.SSLSocketFactory; | ||
|
||
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); | ||
|
||
// bearer:expected java_lang_ssl_hostname_verifier | ||
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; | ||
|
||
// bearer:expected java_lang_ssl_hostname_verifier | ||
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); | ||
|
||
// bearer:expected java_lang_ssl_hostname_verifier | ||
HttpsURLConnection.setDefaultHostnameVerifier(NoopHostnameVerifier.INSTANCE); | ||
|
||
// bearer:expected java_lang_ssl_hostname_verifier | ||
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
socketFactory.setDefaultHostnameVerifier((HostnameVerifier) new NullHostnameVerifier()); | ||
|
||
public class DummyHostnameVerifier implements HostnameVerifier { | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
@Override | ||
public boolean verify(String s, SSLSession sslSession) { | ||
return true; | ||
} | ||
} | ||
HttpsURLConnection.setDefaultHostnameVerifier(new DummyHostnameVerifier()); | ||
|
||
class AllHosts implements HostnameVerifier { | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
public boolean verify(final String hostname, final SSLSession session) { | ||
return true; | ||
} | ||
} | ||
|
||
public void nullKeyManagerForSSLContext(TrustManager[] trustAllCertificates) { | ||
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL"); | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
sc.init(null, tm, null); | ||
|
||
javax.net.ssl.SSLContext sc2 = SSLContext.getInstance("SSL"); | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
sc2.init(null, tm, null); | ||
|
||
SecureRandom rand = new SecureRandom(); | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
sc.init(null, tm, rand); | ||
} | ||
|
||
public void disableCommonNameChecking() { | ||
TLSClientParameters tls = new TLSClientParameters(); | ||
tls.setSSLSocketFactory(sslFactory); | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
tls.setDisableCNCheck(true); | ||
http.setTlsClientParameters(tls); | ||
} | ||
|
||
protected void getAcceptedIssuersOverride() { | ||
TrustManager[] trustAllCerts = new TrustManager[] { | ||
new X509TrustManager() { | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
@Override | ||
public X509Certificate[] getAcceptedIssuers() { | ||
return new java.security.cert.X509Certificate[] {}; | ||
} | ||
|
||
@Override | ||
public void checkClientTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
} | ||
|
||
@Override | ||
public void checkServerTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
} | ||
} | ||
}; | ||
|
||
TrustManager[] victimizedManager = new TrustManager[]{ | ||
new X509TrustManager() { | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
public X509Certificate[] getAcceptedIssuers() { | ||
X509Certificate[] myTrustedAnchors = new X509Certificate[0]; | ||
return myTrustedAnchors; | ||
} | ||
} | ||
}; | ||
} | ||
|
||
final static HostnameVerifier NO_VERIFY = new HostnameVerifier() { | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
public boolean verify(String hostname, SSLSession session) { | ||
return true; | ||
} | ||
}; | ||
|
||
try { | ||
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { | ||
// bearer:expected java_lang_ssl_hostname_verifier | ||
@Override | ||
public boolean verify(String s, SSLSession sslSession) { | ||
return true; | ||
} | ||
}); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
public class MySocketFactorySubClass extends SSLSocketFactory { | ||
SSLContext sslContext = SSLContext.getInstance("TLS"); | ||
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { | ||
super(truststore); | ||
|
||
TrustManager tm = new X509TrustManager() { | ||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { | ||
} | ||
|
||
// bearer:expected java_lang_ssl_hostname_verifier | ||
public X509Certificate[] getAcceptedIssuers() { | ||
return null; | ||
} | ||
}; | ||
|
||
// bearer:expected java_lang_ssl_hostname_verifier | ||
sslContext.init(null, new TrustManager[] { tm }, null); | ||
} | ||
} | ||
|
||
MySocketFactorySubClass socketFactory = new MySocketFactorySubClass(trustStore); | ||
// TODO bearer expected java_lang_ssl_hostname_verifier | ||
socketFactory.setHostnameVerifier(MySocketFactorySubClass.ALLOW_ALL_HOSTNAME_VERIFIER); |