Skip to content

Commit e11cb94

Browse files
Added a query for ignored hostname verification
- Added IgnoredHostnameVerification.ql - Added a qhelp file with examples - Added tests
1 parent 2ecf0d3 commit e11cb94

File tree

7 files changed

+202
-0
lines changed

7 files changed

+202
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
2+
socket.startHandshake();
3+
boolean successful = verifier.verify(host, socket.getSession());
4+
if (!successful) {
5+
socket.close();
6+
throw new SSLException("Oops! Hostname verification failed!");
7+
}
8+
return socket;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
2+
socket.startHandshake();
3+
verifier.verify(host, socket.getSession());
4+
return socket;
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 true if the hostname is acceptable and false 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> 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 exeption is thrown if it 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.veify() method</a>.
40+
</li>
41+
</references>
42+
</qhelp>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 medium
8+
* @id java/ignored-hostname-verification
9+
* @tags security
10+
* external/cwe/cwe-295
11+
*/
12+
13+
import java
14+
import semmle.code.java.controlflow.Guards
15+
import semmle.code.java.dataflow.DataFlow
16+
17+
private class HostnameVerificationCall extends MethodAccess {
18+
HostnameVerificationCall() {
19+
getMethod()
20+
.getDeclaringType()
21+
.getASupertype*()
22+
.hasQualifiedName("javax.net.ssl", "HostnameVerifier") and
23+
getMethod().hasStringSignature("verify(String, SSLSession)")
24+
}
25+
26+
predicate ignored() {
27+
not exists(
28+
DataFlow::Node source, DataFlow::Node sink, CheckFailedHostnameVerificationConfig config
29+
|
30+
this = source.asExpr() and config.hasFlow(source, sink)
31+
)
32+
}
33+
}
34+
35+
private class CheckFailedHostnameVerificationConfig extends DataFlow::Configuration {
36+
CheckFailedHostnameVerificationConfig() { this = "CheckFailedHostnameVerificationConfig" }
37+
38+
override predicate isSource(DataFlow::Node source) {
39+
source.asExpr() instanceof HostnameVerificationCall
40+
}
41+
42+
override predicate isSink(DataFlow::Node sink) {
43+
exists(Guard guard, ThrowStmt throwStmt |
44+
guard.controls(throwStmt.getBasicBlock(), _) and
45+
(
46+
guard.(EqualityTest).getAnOperand() = sink.asExpr() or
47+
guard.(HostnameVerificationCall) = sink.asExpr()
48+
)
49+
)
50+
}
51+
}
52+
53+
from HostnameVerificationCall verification
54+
where verification.ignored()
55+
select verification, "Ignored result of hostname verification."
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| IgnoredHostnameVerification.java:15:5:15:46 | verify(...) | Ignored result of hostname verification. |
2+
| IgnoredHostnameVerification.java:25:22:25:63 | verify(...) | Ignored result of hostname verification. |
3+
| IgnoredHostnameVerification.java:36:22:36:63 | verify(...) | Ignored result of hostname verification. |
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import java.io.IOException;
2+
import javax.net.ssl.HostnameVerifier;
3+
import javax.net.ssl.SSLException;
4+
import javax.net.ssl.SSLSocket;
5+
import javax.net.ssl.SSLSocketFactory;
6+
7+
public class IgnoredHostnameVerification {
8+
9+
// BAD: ignored result of HostnameVerifier.verify()
10+
public static SSLSocket connectWithIgnoredHostnameVerification(
11+
String host, int port, HostnameVerifier verifier) throws IOException {
12+
13+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
14+
socket.startHandshake();
15+
verifier.verify(host, socket.getSession());
16+
return socket;
17+
}
18+
19+
// BAD: ignored result of HostnameVerifier.verify()
20+
public static SSLSocket connectAndOnlyPrintResultOfHostnameVerification(
21+
String host, int port, HostnameVerifier verifier) throws IOException {
22+
23+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
24+
socket.startHandshake();
25+
boolean result = verifier.verify(host, socket.getSession());
26+
System.out.println("Result of hostname verification: " + result);
27+
return socket;
28+
}
29+
30+
// BAD: ignored result of HostnameVerifier.verify()
31+
public static SSLSocket connectAndOnlyPrintFailureOfHostnameVerification(
32+
String host, int port, HostnameVerifier verifier) throws IOException {
33+
34+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
35+
socket.startHandshake();
36+
boolean failed = verifier.verify(host, socket.getSession());
37+
if (failed) {
38+
System.out.println("Hostname verification failed");
39+
}
40+
41+
return socket;
42+
}
43+
44+
// GOOD: connect and check result of HostnameVerifier.verify()
45+
public static SSLSocket connectWithHostnameVerification01(
46+
String host, int port, HostnameVerifier verifier) throws IOException {
47+
48+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
49+
socket.startHandshake();
50+
boolean successful = verifier.verify(host, socket.getSession());
51+
if (successful == false) {
52+
socket.close();
53+
throw new SSLException("Oops! Hostname verification failed!");
54+
}
55+
56+
return socket;
57+
}
58+
59+
// GOOD: connect and check result of HostnameVerifier.verify()
60+
public static SSLSocket connectWithHostnameVerification02(
61+
String host, int port, HostnameVerifier verifier) throws IOException {
62+
63+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
64+
socket.startHandshake();
65+
boolean successful = verifier.verify(host, socket.getSession());
66+
if (!successful) {
67+
socket.close();
68+
throw new SSLException("Oops! Hostname verification failed!");
69+
}
70+
71+
return socket;
72+
}
73+
74+
// GOOD: connect and check result of HostnameVerifier.verify()
75+
public static SSLSocket connectWithHostnameVerification03(
76+
String host, int port, HostnameVerifier verifier) throws IOException {
77+
78+
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
79+
socket.startHandshake();
80+
boolean successful = verifier.verify(host, socket.getSession());
81+
if (successful) {
82+
return socket;
83+
}
84+
85+
socket.close();
86+
throw new SSLException("Oops! Hostname verification failed!");
87+
}
88+
89+
}
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)