Skip to content

Commit

Permalink
feat(java): missing or permissive ssl hostname verifier (CWE-295) (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored Feb 26, 2024
1 parent 830a5ec commit ffc389e
Show file tree
Hide file tree
Showing 3 changed files with 374 additions and 0 deletions.
225 changes: 225 additions & 0 deletions rules/java/lang/ssl_hostname_verifier.yml
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
18 changes: 18 additions & 0 deletions tests/java/lang/ssl_hostname_verifier/test.js
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 tests/java/lang/ssl_hostname_verifier/testdata/main.java
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);

0 comments on commit ffc389e

Please sign in to comment.