Merge pull request #6171 from atorralba/atorralba/promote-unsafe-certificate-trust

Java: Promote Unsafe certificate trust query from experimental
This commit is contained in:
Tony Torralba
2022-01-20 12:07:03 +01:00
committed by GitHub
20 changed files with 791 additions and 291 deletions

View File

@@ -0,0 +1,50 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Java offers two mechanisms for SSL authentication - trust manager and hostname verifier (the later is checked by the <code>java/insecure-hostname-verifier</code> query). The trust manager validates the peer's certificate chain while hostname verification establishes that the hostname in the URL matches the hostname in the server's identification.</p>
<p>When <code>SSLSocket</code> or <code>SSLEngine</code> are created without a secure <code>setEndpointIdentificationAlgorithm</code>, hostname verification is disabled by default.</p>
<p>This query checks whether <code>setEndpointIdentificationAlgorithm</code> is missing, thereby making the application vulnerable to man-in-the-middle attacks. The query also covers insecure configurations of <code>com.rabbitmq.client.ConnectionFactory</code>.</p>
</overview>
<recommendation>
<p>Validate SSL certificates in SSL authentication.</p>
</recommendation>
<example>
<p>The following two examples show two ways of configuring SSLSocket/SSLEngine. In the 'BAD' case,
<code>setEndpointIdentificationAlgorithm</code> is not called, thus no hostname verification takes place. In the 'GOOD' case, <code>setEndpointIdentificationAlgorithm</code> is called.</p>
<sample src="UnsafeCertTrust.java" />
</example>
<references>
<li>
<a href="https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05g-Testing-Network-Communication.md">Testing Endpoint Identify Verification (MSTG-NETWORK-3)</a>.
</li>
<li>
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLParameters.html#setEndpointIdentificationAlgorithm(java.lang.String)">SSLParameters.setEndpointIdentificationAlgorithm documentation</a>.
</li>
<li>
RabbitMQ:
<a href="https://rabbitmq.github.io/rabbitmq-java-client/api/current/com/rabbitmq/client/ConnectionFactory.html#enableHostnameVerification()">ConnectionFactory.enableHostnameVerification documentation</a>.
</li>
<li>
RabbitMQ:
<a href="https://www.rabbitmq.com/ssl.html#java-client">Using TLS in the Java Client</a>.
</li>
<li>
<a href="https://github.com/advisories/GHSA-xvch-r4wf-h8w9">CVE-2018-17187: Apache Qpid Proton-J transport issue with hostname verification</a>.
</li>
<li>
<a href="https://github.com/advisories/GHSA-46j3-r4pj-4835">CVE-2018-8034: Apache Tomcat - host name verification when using TLS with the WebSocket client</a>.
</li>
<li>
<a href="https://github.com/advisories/GHSA-w4g2-9hj6-5472">CVE-2018-11087: Pivotal Spring AMQP vulnerability due to lack of hostname validation</a>.
</li>
<li>
<a href="https://github.com/advisories/GHSA-m9w8-v359-9ffr">CVE-2018-11775: TLS hostname verification issue when using the Apache ActiveMQ Client</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Unsafe certificate trust
* @description SSLSocket/SSLEngine ignores all SSL certificate validation
* errors when establishing an HTTPS connection, thereby making
* the app vulnerable to man-in-the-middle attacks.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/unsafe-cert-trust
* @tags security
* external/cwe/cwe-273
*/
import java
import semmle.code.java.security.UnsafeCertTrustQuery
from Expr unsafeTrust
where
unsafeTrust instanceof RabbitMQEnableHostnameVerificationNotSet or
exists(SslEndpointIdentificationFlowConfig config |
config.hasFlowTo(DataFlow::exprNode(unsafeTrust))
)
select unsafeTrust, "Unsafe configuration of trusted certificates."

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* The query "Unsafe certificate trust" (`java/unsafe-cert-trust`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @luchua-bc](https://github.com/github/codeql/pull/3550).

View File

@@ -1,42 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>When SSLSocket or SSLEngine is created without a valid parameter of setEndpointIdentificationAlgorithm, hostname verification is disabled by default.</p>
<p>Unsafe implementation of the interface X509TrustManager and SSLSocket/SSLEngine ignores all SSL certificate validation errors when establishing an HTTPS connection, thereby making the app vulnerable to man-in-the-middle attacks.</p>
<p>This query checks whether setEndpointIdentificationAlgorithm is missing. The query also covers a special implementation com.rabbitmq.client.ConnectionFactory.</p>
</overview>
<recommendation>
<p>Validate SSL certificate in SSL authentication.</p>
</recommendation>
<example>
<p>The following two examples show two ways of configuring SSLSocket/SSLEngine. In the 'BAD' case,
setEndpointIdentificationAlgorithm is not called, thus no hostname verification takes place. In the 'GOOD' case, setEndpointIdentificationAlgorithm is called.</p>
<sample src="UnsafeCertTrust.java" />
</example>
<references>
<li>
<a href="https://cwe.mitre.org/data/definitions/273.html">CWE-273</a>
</li>
<li>
<a href="https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05g-Testing-Network-Communication.md">Testing Endpoint Identify Verification (MSTG-NETWORK-3)</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-xvch-r4wf-h8w9">CVE-2018-17187: Apache Qpid Proton-J transport issue with hostname verification</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-46j3-r4pj-4835">CVE-2018-8034: Apache Tomcat - host name verification when using TLS with the WebSocket client</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-w4g2-9hj6-5472">CVE-2018-11087: Pivotal Spring AMQP vulnerability due to lack of hostname validation</a>
</li>
<li>
<a href="https://github.com/advisories/GHSA-m9w8-v359-9ffr">CVE-2018-11775: TLS hostname verification issue when using the Apache ActiveMQ Client</a>
</li>
</references>
</qhelp>

View File

@@ -1,169 +0,0 @@
/**
* @name Unsafe certificate trust
* @description SSLSocket/SSLEngine ignores all SSL certificate validation
* errors when establishing an HTTPS connection, thereby making
* the app vulnerable to man-in-the-middle attacks.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/unsafe-cert-trust
* @tags security
* external/cwe/cwe-273
*/
import java
import semmle.code.java.security.Encryption
class SSLEngine extends RefType {
SSLEngine() { this.hasQualifiedName("javax.net.ssl", "SSLEngine") }
}
class Socket extends RefType {
Socket() { this.hasQualifiedName("java.net", "Socket") }
}
class SocketFactory extends RefType {
SocketFactory() { this.hasQualifiedName("javax.net", "SocketFactory") }
}
class SSLSocket extends RefType {
SSLSocket() { this.hasQualifiedName("javax.net.ssl", "SSLSocket") }
}
/**
* has setEndpointIdentificationAlgorithm set correctly
*/
predicate setEndpointIdentificationAlgorithm(MethodAccess createSSL) {
exists(
Variable sslo, MethodAccess ma, Variable sslparams //setSSLParameters with valid setEndpointIdentificationAlgorithm set
|
createSSL = sslo.getAnAssignedValue() and
ma.getQualifier() = sslo.getAnAccess() and
ma.getMethod().hasName("setSSLParameters") and
ma.getArgument(0) = sslparams.getAnAccess() and
exists(MethodAccess setepa |
setepa.getQualifier() = sslparams.getAnAccess() and
setepa.getMethod().hasName("setEndpointIdentificationAlgorithm") and
not setepa.getArgument(0) instanceof NullLiteral
)
)
}
/**
* has setEndpointIdentificationAlgorithm set correctly
*/
predicate hasEndpointIdentificationAlgorithm(Variable ssl) {
exists(
MethodAccess ma, Variable sslparams //setSSLParameters with valid setEndpointIdentificationAlgorithm set
|
ma.getQualifier() = ssl.getAnAccess() and
ma.getMethod().hasName("setSSLParameters") and
ma.getArgument(0) = sslparams.getAnAccess() and
exists(MethodAccess setepa |
setepa.getQualifier() = sslparams.getAnAccess() and
setepa.getMethod().hasName("setEndpointIdentificationAlgorithm") and
not setepa.getArgument(0) instanceof NullLiteral
)
)
}
/**
* Cast of Socket to SSLSocket
*/
predicate sslCast(MethodAccess createSSL) {
exists(Variable ssl, CastExpr ce |
ce.getExpr() = createSSL and
ce.getControlFlowNode().getASuccessor().(VariableAssign).getDestVar() = ssl and
ssl.getType() instanceof SSLSocket //With a type cast `SSLSocket socket = (SSLSocket) socketFactory.createSocket("www.example.com", 443)`
)
}
/**
* SSL object is created in a separate method call or in the same method
*/
predicate hasFlowPath(MethodAccess createSSL, Variable ssl) {
(
createSSL = ssl.getAnAssignedValue()
or
exists(CastExpr ce |
ce.getExpr() = createSSL and
ce.getControlFlowNode().getASuccessor().(VariableAssign).getDestVar() = ssl //With a type cast like SSLSocket socket = (SSLSocket) socketFactory.createSocket("www.example.com", 443);
)
)
or
exists(MethodAccess tranm |
createSSL.getEnclosingCallable() = tranm.getMethod() and
tranm.getControlFlowNode().getASuccessor().(VariableAssign).getDestVar() = ssl and
not setEndpointIdentificationAlgorithm(createSSL) //Check the scenario of invocation before used in the current method
)
}
/**
* Not have the SSLParameter set
*/
predicate hasNoEndpointIdentificationSet(MethodAccess createSSL, Variable ssl) {
//No setSSLParameters set
hasFlowPath(createSSL, ssl) and
not exists(MethodAccess ma |
ma.getQualifier() = ssl.getAnAccess() and
ma.getMethod().hasName("setSSLParameters")
)
or
//No endpointIdentificationAlgorithm set with setSSLParameters
hasFlowPath(createSSL, ssl) and
not setEndpointIdentificationAlgorithm(createSSL)
}
/**
* The setEndpointIdentificationAlgorithm method of SSLParameters with the ssl engine or socket
*/
class SSLEndpointIdentificationNotSet extends MethodAccess {
SSLEndpointIdentificationNotSet() {
(
this.getMethod().hasName("createSSLEngine") and
this.getMethod().getDeclaringType() instanceof SSLContext //createEngine method of SSLContext
or
this.getMethod().hasName("createSocket") and
this.getMethod().getDeclaringType() instanceof SocketFactory and
this.getMethod().getReturnType() instanceof Socket and
sslCast(this) //createSocket method of SocketFactory
) and
exists(Variable ssl |
hasNoEndpointIdentificationSet(this, ssl) and //Not set in itself
not exists(VariableAssign ar, Variable newSsl |
ar.getSource() = this.getCaller().getAReference() and
ar.getDestVar() = newSsl and
hasEndpointIdentificationAlgorithm(newSsl) //Not set in its caller either
)
) and
not exists(MethodAccess ma | ma.getMethod() instanceof HostnameVerifierVerify) //Reduce false positives since this method access set default hostname verifier
}
}
class RabbitMQConnectionFactory extends RefType {
RabbitMQConnectionFactory() { this.hasQualifiedName("com.rabbitmq.client", "ConnectionFactory") }
}
/**
* The com.rabbitmq.client.ConnectionFactory useSslProtocol method access without enableHostnameVerification
*/
class RabbitMQEnableHostnameVerificationNotSet extends MethodAccess {
RabbitMQEnableHostnameVerificationNotSet() {
this.getMethod().hasName("useSslProtocol") and
this.getMethod().getDeclaringType() instanceof RabbitMQConnectionFactory and
exists(Variable v |
v.getType() instanceof RabbitMQConnectionFactory and
this.getQualifier() = v.getAnAccess() and
not exists(MethodAccess ma |
ma.getMethod().hasName("enableHostnameVerification") and
ma.getQualifier() = v.getAnAccess()
)
)
}
}
from MethodAccess aa
where
aa instanceof SSLEndpointIdentificationNotSet or
aa instanceof RabbitMQEnableHostnameVerificationNotSet
select aa, "Unsafe configuration of trusted certificates"

View File

@@ -94,18 +94,6 @@ class UnsafeTlsVersion extends StringLiteral {
}
}
class SSLParameters extends RefType {
SSLParameters() { hasQualifiedName("javax.net.ssl", "SSLParameters") }
}
class SSLSocket extends RefType {
SSLSocket() { hasQualifiedName("javax.net.ssl", "SSLSocket") }
}
class SSLServerSocket extends RefType {
SSLServerSocket() { hasQualifiedName("javax.net.ssl", "SSLServerSocket") }
}
class SSLEngine extends RefType {
SSLEngine() { hasQualifiedName("javax.net.ssl", "SSLEngine") }
}