Skip to content

Commit fd4dc95

Browse files
authored
Merge pull request github#6443 from artem-smotrakov/ignored-hostname-verifier
Java: An experimental query for ignored hostname verification
2 parents de5b3a2 + f2bc584 commit fd4dc95

File tree

7 files changed

+201
-0
lines changed

7 files changed

+201
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public SSLSocket connect(String host, int port, HostnameVerifier verifier) {
2+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
3+
socket.startHandshake();
4+
boolean successful = verifier.verify(host, socket.getSession());
5+
if (!successful) {
6+
socket.close();
7+
throw new SSLException("Oops! Hostname verification failed!");
8+
}
9+
return socket;
10+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public SSLSocket connect(String host, int port, HostnameVerifier verifier) {
2+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
3+
socket.startHandshake();
4+
verifier.verify(host, socket.getSession());
5+
return socket;
6+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
The method <code>HostnameVerifier.verify()</code> checks that the hostname from the server's certificate
7+
matches the server hostname after an HTTPS connection is established.
8+
The method returns <code>true</code> if the hostname is acceptable and <code>false</code> otherwise. The contract of the method
9+
does not require it to throw an exception if the verification failed.
10+
Therefore, a caller has to check the result and drop the connection if the hostname verification failed.
11+
Otherwise, an attacker may be able to implement a man-in-the-middle attack and impersonate the server.
12+
</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>
17+
Always check the result of <code>HostnameVerifier.verify()</code> and drop the connection
18+
if the method returns false.
19+
</p>
20+
</recommendation>
21+
22+
<example>
23+
<p>
24+
In the following example, the method <code>HostnameVerifier.verify()</code> is called but its result is ignored.
25+
As a result, no hostname verification actually happens.
26+
</p>
27+
<sample src="IgnoredHostnameVerification.java" />
28+
29+
<p>
30+
In the next example, the result of the <code>HostnameVerifier.verify()</code> method is checked
31+
and an exception is thrown if the verification failed.
32+
</p>
33+
<sample src="CheckedHostnameVerification.java" />
34+
</example>
35+
36+
<references>
37+
<li>
38+
Java API Specification:
39+
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/HostnameVerifier.html#verify(java.lang.String,javax.net.ssl.SSLSession)">HostnameVerifier.verify() method</a>.
40+
</li>
41+
</references>
42+
</qhelp>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @name Ignored result of hostname verification
3+
* @description The method HostnameVerifier.verify() returns a result of hostname verification.
4+
* A caller has to check the result and drop the connection if the verification failed.
5+
* @kind problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/ignored-hostname-verification
9+
* @tags security
10+
* external/cwe/cwe-297
11+
*/
12+
13+
import java
14+
import semmle.code.java.security.Encryption
15+
16+
/** A `HostnameVerifier.verify()` call that is not wrapped in another `HostnameVerifier`. */
17+
private class HostnameVerificationCall extends MethodAccess {
18+
HostnameVerificationCall() {
19+
this.getMethod() instanceof HostnameVerifierVerify and
20+
not this.getCaller() instanceof HostnameVerifierVerify
21+
}
22+
23+
/** Holds if the result of the call is not used. */
24+
predicate isIgnored() { this = any(ExprStmt es).getExpr() }
25+
}
26+
27+
from HostnameVerificationCall verification
28+
where verification.isIgnored()
29+
select verification, "Ignored result of hostname verification."
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| IgnoredHostnameVerification.java:16:5:16:46 | verify(...) | Ignored result of hostname verification. |
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import java.io.IOException;
2+
import javax.net.ssl.HostnameVerifier;
3+
import javax.net.ssl.SSLException;
4+
import javax.net.ssl.SSLSession;
5+
import javax.net.ssl.SSLSocket;
6+
import javax.net.ssl.SSLSocketFactory;
7+
8+
public class IgnoredHostnameVerification {
9+
10+
// BAD: ignored result of HostnameVerifier.verify()
11+
public static SSLSocket connectWithIgnoredHostnameVerification(
12+
String host, int port, HostnameVerifier verifier) throws IOException {
13+
14+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
15+
socket.startHandshake();
16+
verifier.verify(host, socket.getSession());
17+
return socket;
18+
}
19+
20+
public static void check(boolean result) throws SSLException {
21+
if (!result) {
22+
throw new SSLException("Oops! Hostname verification failed!");
23+
}
24+
}
25+
26+
// GOOD: connect and check result of HostnameVerifier.verify()
27+
public static SSLSocket connectWithHostnameVerification00(
28+
String host, int port, HostnameVerifier verifier) throws IOException {
29+
30+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
31+
socket.startHandshake();
32+
check(verifier.verify(host, socket.getSession()));
33+
return socket;
34+
}
35+
36+
// GOOD: connect and check result of HostnameVerifier.verify()
37+
public static SSLSocket connectWithHostnameVerification01(
38+
String host, int port, HostnameVerifier verifier) throws IOException {
39+
40+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
41+
socket.startHandshake();
42+
boolean successful = verifier.verify(host, socket.getSession());
43+
if (successful == false) {
44+
socket.close();
45+
throw new SSLException("Oops! Hostname verification failed!");
46+
}
47+
48+
return socket;
49+
}
50+
51+
// GOOD: connect and check result of HostnameVerifier.verify()
52+
public static SSLSocket connectWithHostnameVerification02(
53+
String host, int port, HostnameVerifier verifier) throws IOException {
54+
55+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
56+
socket.startHandshake();
57+
boolean successful = false;
58+
if (verifier != null) {
59+
successful = verifier.verify(host, socket.getSession());
60+
}
61+
if (!successful) {
62+
socket.close();
63+
throw new SSLException("Oops! Hostname verification failed!");
64+
}
65+
66+
return socket;
67+
}
68+
69+
// GOOD: connect and check result of HostnameVerifier.verify()
70+
public static SSLSocket connectWithHostnameVerification03(
71+
String host, int port, HostnameVerifier verifier) throws IOException {
72+
73+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
74+
socket.startHandshake();
75+
boolean successful = verifier.verify(host, socket.getSession());
76+
if (successful) {
77+
return socket;
78+
}
79+
80+
socket.close();
81+
throw new SSLException("Oops! Hostname verification failed!");
82+
}
83+
84+
// GOOD: connect and check result of HostnameVerifier.verify()
85+
public static String connectWithHostnameVerification04(
86+
String[] hosts, HostnameVerifier verifier, SSLSession session) throws IOException {
87+
88+
for (String host : hosts) {
89+
if (verifier.verify(host, session)) {
90+
return host;
91+
}
92+
}
93+
94+
throw new SSLException("Oops! Hostname verification failed!");
95+
}
96+
97+
public static class HostnameVerifierWrapper implements HostnameVerifier {
98+
99+
private final HostnameVerifier verifier;
100+
101+
public HostnameVerifierWrapper(HostnameVerifier verifier) {
102+
this.verifier = verifier;
103+
}
104+
105+
@Override
106+
public boolean verify(String hostname, SSLSession session) {
107+
return verifier.verify(hostname, session); // GOOD: wrapped calls should not be reported
108+
}
109+
110+
}
111+
112+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-297/IgnoredHostnameVerification.ql

0 commit comments

Comments
 (0)