Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3da1cb0

Browse files
committed
Java: Add unsafe hostname verification query
1 parent 8df5d77 commit 3da1cb0

6 files changed

Lines changed: 253 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
public static void main(String[] args) {
2+
3+
{
4+
HostnameVerifier verifier = new HostnameVerifier() {
5+
@Override
6+
public boolean verify(String hostname, SSLSession session) {
7+
return true; // BAD: accept even if the hostname doesn't match
8+
}
9+
};
10+
HttpsURLConnection.setDefaultHostnameVerifier(verifier);
11+
}
12+
13+
{
14+
HostnameVerifier verifier = new HostnameVerifier() {
15+
@Override
16+
public boolean verify(String hostname, SSLSession session) {
17+
try { // GOOD: verify the certificate
18+
Certificate[] certs = session.getPeerCertificates();
19+
X509Certificate x509 = (X509Certificate) certs[0];
20+
check(new String[]{host}, x509);
21+
return true;
22+
} catch (SSLException e) {
23+
return false;
24+
}
25+
}
26+
};
27+
HttpsURLConnection.setDefaultHostnameVerifier(verifier);
28+
}
29+
30+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
If a <code>HostnameVerifier</code> always returns <code>true</code> it will not verify the hostname at all.
8+
This allows an attacker to perform a Man-in-the-middle attack against the application therefore breaking any security Transport Layer Security (TLS) gives.
9+
10+
An attack would look like this:
11+
1. The program connects to <code>https://example.com</code>.
12+
2. The attacker intercepts this connection and presents one of their valid certificates they control, for example one from Let's Encrypt.
13+
3. Java verifies that the certificate has been issued by a trusted certificate authority.
14+
4. Java verifies that the certificate has been issued for the host <code>example.com</code>, which will fail because the certificate has been issued for <code>malicious.domain</code>.
15+
5. Java wants to reject the certificate because the hostname does not match. Before doing this it checks whether there exists a <code>HostnameVerifier</code>.
16+
6. Your <code>HostnameVerifier</code> is called which returns <code>true</code> for any certificate so also for this one.
17+
7. Java proceeds with the connection since your <code>HostnameVerifier</code> accepted it.
18+
8. The attacker can now read the data (Man-in-the-middle) your program sends to <code>https://example.com</code> while the program thinks the connection is secure.
19+
</p>
20+
</overview>
21+
22+
<recommendation>
23+
<p>
24+
Do NOT use an unverifying <code>HostnameVerifier</code>!
25+
<li>If you use an unverifying verifier to solve a configuration problem with TLS/HTTPS you should solve the configuration problem instead.
26+
</li>
27+
</p>
28+
29+
</recommendation>
30+
31+
<example>
32+
<p>
33+
In the first (bad) example, the <code>HostnameVerifier</code> always returns <code>true</code>.
34+
This allows an attacker to perform a man-in-the-middle attack, because any certificate is accepted despite an incorrect hostname.
35+
In the second (good) example, the <code>HostnameVerifier</code> only returns <code>true</code> when the certificate has been correctly checked.
36+
</p>
37+
<sample src="UnsafeHostnameVerification.java" />
38+
</example>
39+
40+
<references>
41+
<li><a href="https://developer.android.com/training/articles/security-ssl">Android Security Guide for TLS/HTTPS</a>.</li>
42+
<li><a href="https://tersesystems.com/blog/2014/03/23/fixing-hostname-verification/">Further Information on Hostname Verification</a>.</li>
43+
<li>OWASP: <a href="https://cwe.mitre.org/data/definitions/297.html">CWE-297</a>.</li>
44+
</references>
45+
</qhelp>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @name Disabled hostname verification
3+
* @description Accepting any certificate as valid for a host allows an attacker to perform a man-in-the-middle attack.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id java/everything-accepting-hostname-verifier
8+
* @tags security
9+
* external/cwe/cwe-297
10+
*/
11+
12+
import java
13+
import semmle.code.java.security.Encryption
14+
import semmle.code.java.dataflow.DataFlow
15+
import DataFlow::PathGraph
16+
import semmle.code.java.controlflow.Guards
17+
18+
/**
19+
* Holds if `m` always returns `true` ignoring any exceptional flow.
20+
*/
21+
private predicate alwaysReturnsTrue(HostnameVerifierVerify m) {
22+
forex(ReturnStmt rs | rs.getEnclosingCallable() = m |
23+
rs.getResult().(CompileTimeConstantExpr).getBooleanValue() = true
24+
)
25+
}
26+
27+
/**
28+
* A class that overrides the `javax.net.ssl.HostnameVerifier.verify` method and **always** returns `true`, thus
29+
* accepting any certificate despite a hostname mismatch.
30+
*/
31+
class TrustAllHostnameVerifier extends RefType {
32+
TrustAllHostnameVerifier() {
33+
this.getASupertype*() instanceof HostnameVerifier and
34+
exists(HostnameVerifierVerify m |
35+
m.getDeclaringType() = this and
36+
alwaysReturnsTrue(m)
37+
)
38+
}
39+
}
40+
41+
/**
42+
* A configuration to model the flow of a `TrustAllHostnameVerifier` to a `set(Default)HostnameVerifier` call.
43+
*/
44+
class TrustAllHostnameVerifierConfiguration extends DataFlow::Configuration {
45+
TrustAllHostnameVerifierConfiguration() { this = "TrustAllHostnameVerifierConfiguration" }
46+
47+
override predicate isSource(DataFlow::Node source) {
48+
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof TrustAllHostnameVerifier
49+
}
50+
51+
override predicate isSink(DataFlow::Node sink) {
52+
exists(MethodAccess ma, Method m |
53+
(m instanceof SetDefaultHostnameVerifierMethod or m instanceof SetHostnameVerifierMethod) and
54+
ma.getMethod() = m
55+
|
56+
ma.getArgument(0) = sink.asExpr()
57+
)
58+
}
59+
}
60+
61+
/** Holds if `node` is guarded by a flag that suggests an intentionally insecure feature. */
62+
private predicate isNodeGuardedByFlag(DataFlow::Node node) {
63+
exists(Guard g | g.controls(node.asExpr().getBasicBlock(), _) |
64+
g
65+
.(VarAccess)
66+
.getVariable()
67+
.getName()
68+
.regexpMatch("(?i).*(secure|(en|dis)able|selfCert|selfSign|validat|verif|trust|ignore).*")
69+
)
70+
}
71+
72+
from DataFlow::PathNode source, DataFlow::PathNode sink, TrustAllHostnameVerifierConfiguration cfg
73+
where cfg.hasFlowPath(source, sink) and not isNodeGuardedByFlag(sink.getNode())
74+
select sink, source, sink, "$@ that accepts any certificate as valid, is used here.", source,
75+
"This hostname verifier"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
edges
2+
| UnsafeHostnameVerification.java:68:31:73:3 | new (...) : new HostnameVerifier(...) { ... } | UnsafeHostnameVerification.java:74:49:74:56 | verifier |
3+
| UnsafeHostnameVerification.java:77:69:82:2 | new (...) : new HostnameVerifier(...) { ... } | UnsafeHostnameVerification.java:33:50:33:76 | ALLOW_ALL_HOSTNAME_VERIFIER |
4+
nodes
5+
| UnsafeHostnameVerification.java:13:49:18:3 | new (...) | semmle.label | new (...) |
6+
| UnsafeHostnameVerification.java:25:49:25:65 | ...->... | semmle.label | ...->... |
7+
| UnsafeHostnameVerification.java:33:50:33:76 | ALLOW_ALL_HOSTNAME_VERIFIER | semmle.label | ALLOW_ALL_HOSTNAME_VERIFIER |
8+
| UnsafeHostnameVerification.java:46:49:46:65 | ...->... | semmle.label | ...->... |
9+
| UnsafeHostnameVerification.java:58:50:58:76 | ...->... | semmle.label | ...->... |
10+
| UnsafeHostnameVerification.java:68:31:73:3 | new (...) : new HostnameVerifier(...) { ... } | semmle.label | new (...) : new HostnameVerifier(...) { ... } |
11+
| UnsafeHostnameVerification.java:74:49:74:56 | verifier | semmle.label | verifier |
12+
| UnsafeHostnameVerification.java:77:69:82:2 | new (...) : new HostnameVerifier(...) { ... } | semmle.label | new (...) : new HostnameVerifier(...) { ... } |
13+
#select
14+
| UnsafeHostnameVerification.java:13:49:18:3 | new (...) | UnsafeHostnameVerification.java:13:49:18:3 | new (...) | UnsafeHostnameVerification.java:13:49:18:3 | new (...) | $@ that accepts any certificate as valid, is used here. | UnsafeHostnameVerification.java:13:49:18:3 | new (...) | This hostname verifier |
15+
| UnsafeHostnameVerification.java:25:49:25:65 | ...->... | UnsafeHostnameVerification.java:25:49:25:65 | ...->... | UnsafeHostnameVerification.java:25:49:25:65 | ...->... | $@ that accepts any certificate as valid, is used here. | UnsafeHostnameVerification.java:25:49:25:65 | ...->... | This hostname verifier |
16+
| UnsafeHostnameVerification.java:46:49:46:65 | ...->... | UnsafeHostnameVerification.java:46:49:46:65 | ...->... | UnsafeHostnameVerification.java:46:49:46:65 | ...->... | $@ that accepts any certificate as valid, is used here. | UnsafeHostnameVerification.java:46:49:46:65 | ...->... | This hostname verifier |
17+
| UnsafeHostnameVerification.java:58:50:58:76 | ...->... | UnsafeHostnameVerification.java:58:50:58:76 | ...->... | UnsafeHostnameVerification.java:58:50:58:76 | ...->... | $@ that accepts any certificate as valid, is used here. | UnsafeHostnameVerification.java:58:50:58:76 | ...->... | This hostname verifier |
18+
| UnsafeHostnameVerification.java:74:49:74:56 | verifier | UnsafeHostnameVerification.java:68:31:73:3 | new (...) : new HostnameVerifier(...) { ... } | UnsafeHostnameVerification.java:74:49:74:56 | verifier | $@ that accepts any certificate as valid, is used here. | UnsafeHostnameVerification.java:68:31:73:3 | new (...) : new HostnameVerifier(...) { ... } | This hostname verifier |
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import javax.net.ssl.HostnameVerifier;
2+
import javax.net.ssl.HttpsURLConnection;
3+
import javax.net.ssl.SSLSession;
4+
5+
public class UnsafeHostnameVerification {
6+
7+
private static final boolean DISABLE_VERIFICATION = true;
8+
9+
/**
10+
* Test the implementation of trusting all hostnames as an anonymous class
11+
*/
12+
public void testTrustAllHostnameOfAnonymousClass() {
13+
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
14+
@Override
15+
public boolean verify(String hostname, SSLSession session) {
16+
return true; // BAD, always returns true
17+
}
18+
});
19+
}
20+
21+
/**
22+
* Test the implementation of trusting all hostnames as a lambda.
23+
*/
24+
public void testTrustAllHostnameLambda() {
25+
HttpsURLConnection.setDefaultHostnameVerifier((name, s) -> true); // BAD, always returns true
26+
}
27+
28+
/**
29+
* Test an all-trusting hostname verifier that is guarded by a flag
30+
*/
31+
public void testGuardedByFlagTrustAllHostname() {
32+
if (DISABLE_VERIFICATION) {
33+
HttpsURLConnection.setDefaultHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER); // GOOD: The all-trusting
34+
// hostname verifier is guarded
35+
// by a feature flag
36+
}
37+
}
38+
39+
public void testGuardedByFlagAccrossCalls() {
40+
if (DISABLE_VERIFICATION) {
41+
functionThatActuallyDisablesVerification();
42+
}
43+
}
44+
45+
private void functionThatActuallyDisablesVerification() {
46+
HttpsURLConnection.setDefaultHostnameVerifier((name, s) -> true); // GOOD [but detected as BAD], because we only
47+
// check guards inside a function
48+
// and not accross function calls. This is considerer GOOD because the call to
49+
// `functionThatActuallyDisablesVerification` is guarded by a feature flag in
50+
// `testGuardedByFlagAccrossCalls`.
51+
// Although this is not ideal as another function could directly call
52+
// `functionThatActuallyDisablesVerification` WITHOUT checking the feature flag.
53+
}
54+
55+
public void testTrustAllHostnameDependingOnDerivedValue() {
56+
String enabled = System.getProperty("disableHostnameVerification");
57+
if (Boolean.parseBoolean(enabled)) {
58+
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); // GOOD [but detected as BAD].
59+
// This is GOOD, because it depends on a feature
60+
// flag, but this is not detected by the query.
61+
}
62+
}
63+
64+
/**
65+
* Test the implementation of trusting all hostnames as a variable
66+
*/
67+
public void testTrustAllHostnameOfVariable() {
68+
HostnameVerifier verifier = new HostnameVerifier() {
69+
@Override
70+
public boolean verify(String hostname, SSLSession session) {
71+
return true; // BAD, always returns true
72+
}
73+
};
74+
HttpsURLConnection.setDefaultHostnameVerifier(verifier);
75+
}
76+
77+
public static final HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER = new HostnameVerifier() {
78+
@Override
79+
public boolean verify(String hostname, SSLSession session) {
80+
return true; // BAD, always returns true
81+
}
82+
};
83+
}
84+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-297/UnsafeHostnameVerification.ql

0 commit comments

Comments
 (0)