Added a query for ignored hostname verification

- Added IgnoredHostnameVerification.ql
- Added a qhelp file with examples
- Added tests
This commit is contained in:
Fosstars
2021-08-08 11:10:44 +02:00
committed by Artem Smotrakov
parent 2ecf0d3264
commit e11cb943a6
7 changed files with 202 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean successful = verifier.verify(host, socket.getSession());
if (!successful) {
socket.close();
throw new SSLException("Oops! Hostname verification failed!");
}
return socket;

View File

@@ -0,0 +1,4 @@
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
verifier.verify(host, socket.getSession());
return socket;

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
The method <code>HostnameVerifier.verify()</code> checks that the hostname from the server's certificate
matches the server hostname after an HTTPS connection is established.
The method returns true if the hostname is acceptable and false otherwise. The contract of the method
does not require it to throw an exception if the verification failed.
Therefore, a caller has to check the result and drop the connection if the hostname verification failed.
Otherwise, an attacker may be able to implement a man-in-the-middle attack and impersonate the server.
</p>
</overview>
<recommendation>
<p>
Always check the result of <code>HostnameVerifier.verify()</code> and drop the connection
if the method returns false.
</p>
</recommendation>
<example>
<p>
In the following example, the method <code>HostnameVerifier.verify()</code> but its result is ignored.
As a result, no hostname verification actually happens.
</p>
<sample src="IgnoredHostnameVerification.java" />
<p>
In the next example, the result of the <code>HostnameVerifier.verify()</code> method is checked
and an exeption is thrown if it the verification failed.
</p>
<sample src="CheckedHostnameVerification.java" />
</example>
<references>
<li>
Java API Specification:
<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>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,55 @@
/**
* @name Ignored result of hostname verification
* @description The method HostnameVerifier.verify() returns a result of hostname verification.
* A caller has to check the result and drop the connection if the verification failed.
* @kind problem
* @problem.severity error
* @precision medium
* @id java/ignored-hostname-verification
* @tags security
* external/cwe/cwe-295
*/
import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.DataFlow
private class HostnameVerificationCall extends MethodAccess {
HostnameVerificationCall() {
getMethod()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("javax.net.ssl", "HostnameVerifier") and
getMethod().hasStringSignature("verify(String, SSLSession)")
}
predicate ignored() {
not exists(
DataFlow::Node source, DataFlow::Node sink, CheckFailedHostnameVerificationConfig config
|
this = source.asExpr() and config.hasFlow(source, sink)
)
}
}
private class CheckFailedHostnameVerificationConfig extends DataFlow::Configuration {
CheckFailedHostnameVerificationConfig() { this = "CheckFailedHostnameVerificationConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof HostnameVerificationCall
}
override predicate isSink(DataFlow::Node sink) {
exists(Guard guard, ThrowStmt throwStmt |
guard.controls(throwStmt.getBasicBlock(), _) and
(
guard.(EqualityTest).getAnOperand() = sink.asExpr() or
guard.(HostnameVerificationCall) = sink.asExpr()
)
)
}
}
from HostnameVerificationCall verification
where verification.ignored()
select verification, "Ignored result of hostname verification."

View File

@@ -0,0 +1,3 @@
| IgnoredHostnameVerification.java:15:5:15:46 | verify(...) | Ignored result of hostname verification. |
| IgnoredHostnameVerification.java:25:22:25:63 | verify(...) | Ignored result of hostname verification. |
| IgnoredHostnameVerification.java:36:22:36:63 | verify(...) | Ignored result of hostname verification. |

View File

@@ -0,0 +1,89 @@
import java.io.IOException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class IgnoredHostnameVerification {
// BAD: ignored result of HostnameVerifier.verify()
public static SSLSocket connectWithIgnoredHostnameVerification(
String host, int port, HostnameVerifier verifier) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
verifier.verify(host, socket.getSession());
return socket;
}
// BAD: ignored result of HostnameVerifier.verify()
public static SSLSocket connectAndOnlyPrintResultOfHostnameVerification(
String host, int port, HostnameVerifier verifier) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean result = verifier.verify(host, socket.getSession());
System.out.println("Result of hostname verification: " + result);
return socket;
}
// BAD: ignored result of HostnameVerifier.verify()
public static SSLSocket connectAndOnlyPrintFailureOfHostnameVerification(
String host, int port, HostnameVerifier verifier) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean failed = verifier.verify(host, socket.getSession());
if (failed) {
System.out.println("Hostname verification failed");
}
return socket;
}
// GOOD: connect and check result of HostnameVerifier.verify()
public static SSLSocket connectWithHostnameVerification01(
String host, int port, HostnameVerifier verifier) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean successful = verifier.verify(host, socket.getSession());
if (successful == false) {
socket.close();
throw new SSLException("Oops! Hostname verification failed!");
}
return socket;
}
// GOOD: connect and check result of HostnameVerifier.verify()
public static SSLSocket connectWithHostnameVerification02(
String host, int port, HostnameVerifier verifier) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean successful = verifier.verify(host, socket.getSession());
if (!successful) {
socket.close();
throw new SSLException("Oops! Hostname verification failed!");
}
return socket;
}
// GOOD: connect and check result of HostnameVerifier.verify()
public static SSLSocket connectWithHostnameVerification03(
String host, int port, HostnameVerifier verifier) throws IOException {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
socket.startHandshake();
boolean successful = verifier.verify(host, socket.getSession());
if (successful) {
return socket;
}
socket.close();
throw new SSLException("Oops! Hostname verification failed!");
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-297/IgnoredHostnameVerification.ql