Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor into libraries
  • Loading branch information
atorralba committed Jan 20, 2022
commit ab4dc30f5457b9de8b3daf6adc77e655c306dacb
101 changes: 101 additions & 0 deletions java/ql/lib/semmle/code/java/security/InsecureTrustManager.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import java
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.security.Encryption
private import semmle.code.java.security.SecurityFlag

/** The creation of an insecure `TrustManager`. */
abstract class InsecureTrustManagerSource extends DataFlow::Node { }

private class DefaultInsecureTrustManagerSource extends InsecureTrustManagerSource {
DefaultInsecureTrustManagerSource() {
this.asExpr().(ClassInstanceExpr).getConstructedType() instanceof InsecureX509TrustManager
}
}

/**
* The use of a `TrustManager` in an SSL context.
* Intentionally insecure connections are not considered sinks.
*/
abstract class InsecureTrustManagerSink extends DataFlow::Node {
InsecureTrustManagerSink() { not isGuardedByInsecureFlag(this) }
}

private class DefaultInsecureTrustManagerSink extends InsecureTrustManagerSink {
DefaultInsecureTrustManagerSink() {
exists(MethodAccess ma, Method m |
m.hasName("init") and
m.getDeclaringType() instanceof SSLContext and
ma.getMethod() = m
|
ma.getArgument(1) = this.asExpr()
)
}
}

/** Holds if `node` is guarded by a flag that suggests an intentionally insecure use. */
private predicate isGuardedByInsecureFlag(DataFlow::Node node) {
exists(Guard g | g.controls(node.asExpr().getBasicBlock(), _) |
g = getASecurityFeatureFlagGuard() or g = getAnInsecureTrustManagerFlagGuard()
)
}

/**
* An insecure `X509TrustManager`.
* An `X509TrustManager` is considered insecure if it never throws a `CertificateException`
* and therefore implicitly trusts any certificate as valid.
*/
private class InsecureX509TrustManager extends RefType {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting here, because I can not comment down there, because the code did not change.
When I coded this, there was no good place for CertificateException (Line 37/61), maybe there is now a better place or common library?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the moment, this is something that is only used here, and it doesn't seem to fit in any of the currently existing libraries. Nonetheless, this is definitely something to keep in mind if, in the future, CertificateException needs to be reused, or more classes from java.security.cert are modeled. Thanks!

InsecureX509TrustManager() {
this.getASupertype*() instanceof X509TrustManager and
exists(Method m |
m.getDeclaringType() = this and
m.hasName("checkServerTrusted") and
not mayThrowCertificateException(m)
)
}
}

/** The `java.security.cert.CertificateException` class. */
private class CertificateException extends RefType {
CertificateException() { this.hasQualifiedName("java.security.cert", "CertificateException") }
}

/**
* Holds if:
* - `m` may `throw` a `CertificateException`, or
* - `m` calls another method that may throw, or
* - `m` calls a method declared to throw a `CertificateException`, but for which no source is available
*/
private predicate mayThrowCertificateException(Method m) {
exists(ThrowStmt throwStmt |
throwStmt.getThrownExceptionType().getASupertype*() instanceof CertificateException
|
throwStmt.getEnclosingCallable() = m
)
or
exists(Method otherMethod | m.polyCalls(otherMethod) |
mayThrowCertificateException(otherMethod)
or
not otherMethod.fromSource() and
otherMethod.getAnException().getType().getASupertype*() instanceof CertificateException
)
}

/**
* Flags suggesting a deliberately insecure `TrustManager` usage.
*/
private class InsecureTrustManagerFlag extends FlagKind {
InsecureTrustManagerFlag() { this = "InsecureTrustManagerFlag" }

bindingset[result]
override string getAFlagName() {
result
.regexpMatch("(?i).*(secure|disable|selfCert|selfSign|validat|verif|trust|ignore|nocertificatecheck).*") and
result != "equalsIgnoreCase"
}
}

/** Gets a guard that represents a (likely) flag controlling an insecure `TrustManager` use. */
private Guard getAnInsecureTrustManagerFlagGuard() {
result = any(InsecureTrustManagerFlag flag).getAFlag().asExpr()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** Provides taint tracking configurations to be used in Trust Manager queries. */

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.Encryption
import semmle.code.java.security.InsecureTrustManager

/**
* A configuration to model the flow of an insecure `TrustManager`
* to the initialization of an SSL context.
*/
class InsecureTrustManagerConfiguration extends TaintTracking::Configuration {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be changed to DataFlow? I've used taint tracking due to limitations of data flow at the time.
See this comment from @aschackmull:
#4879 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataFlow could be used, but then we would need to allow Array implicit reads in the sinks (and potentially in the additional flow steps). Something like:

override predicate allowImplicitRead(DataFlow::Node node, DataFlow::Content c) {
  (this.isSink(node) or this.isAdditionalFlowStep(node, _)) and
  node.getType() instanceof Array and
  c instanceof DataFlow::ArrayContent
}

(which is actually part of the default implicit reads in TaintTracking).

We could even restrict node to be an array of TrustManagers, since that's what the sink is expecting.

Of course, the trade-off would be that, if for some reason the insecure TrustManager temporary goes through more complex data structures, e.g. a Map, we'd lose flows that a TaintTracking::Configuration would catch, but that doesn't sound like something that happens often.

@aschackmull what do you think is more appropriate in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this in 967308f.

InsecureTrustManagerConfiguration() { this = "InsecureTrustManagerConfiguration" }

override predicate isSource(DataFlow::Node source) {
source instanceof InsecureTrustManagerSource
}

override predicate isSink(DataFlow::Node sink) { sink instanceof InsecureTrustManagerSink }
}
107 changes: 5 additions & 102 deletions java/ql/src/Security/CWE/CWE-295/InsecureTrustManager.ql
Original file line number Diff line number Diff line change
Expand Up @@ -10,108 +10,11 @@
*/

import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.Encryption
import semmle.code.java.security.SecurityFlag
import semmle.code.java.security.InsecureTrustManagerQuery
import DataFlow::PathGraph

/**
* An insecure `X509TrustManager`.
* An `X509TrustManager` is considered insecure if it never throws a `CertificateException`
* and therefore implicitly trusts any certificate as valid.
*/
class InsecureX509TrustManager extends RefType {
InsecureX509TrustManager() {
this.getASupertype*() instanceof X509TrustManager and
exists(Method m |
m.getDeclaringType() = this and
m.hasName("checkServerTrusted") and
not mayThrowCertificateException(m)
)
}
}

/** The `java.security.cert.CertificateException` class. */
private class CertificateException extends RefType {
CertificateException() { this.hasQualifiedName("java.security.cert", "CertificateException") }
}

/**
* Holds if:
* - `m` may `throw` a `CertificateException`, or
* - `m` calls another method that may throw, or
* - `m` calls a method declared to throw a `CertificateException`, but for which no source is available
*/
private predicate mayThrowCertificateException(Method m) {
exists(ThrowStmt throwStmt |
throwStmt.getThrownExceptionType().getASupertype*() instanceof CertificateException
|
throwStmt.getEnclosingCallable() = m
)
or
exists(Method otherMethod | m.polyCalls(otherMethod) |
mayThrowCertificateException(otherMethod)
or
not otherMethod.fromSource() and
otherMethod.getAnException().getType().getASupertype*() instanceof CertificateException
)
}

/**
* A configuration to model the flow of an `InsecureX509TrustManager` to an `SSLContext.init` call.
*/
class InsecureTrustManagerConfiguration extends TaintTracking::Configuration {
InsecureTrustManagerConfiguration() { this = "InsecureTrustManagerConfiguration" }

override predicate isSource(DataFlow::Node source) {
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof InsecureX509TrustManager
}

override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, Method m |
m.hasName("init") and
m.getDeclaringType() instanceof SSLContext and
ma.getMethod() = m
|
ma.getArgument(1) = sink.asExpr()
)
}
}

/**
* Flags suggesting a deliberately insecure `TrustManager` usage.
*/
private class InsecureTrustManagerFlag extends FlagKind {
InsecureTrustManagerFlag() { this = "InsecureTrustManagerFlag" }

bindingset[result]
override string getAFlagName() {
result
.regexpMatch("(?i).*(secure|disable|selfCert|selfSign|validat|verif|trust|ignore|nocertificatecheck).*") and
result != "equalsIgnoreCase"
}
}

/** Gets a guard that represents a (likely) flag controlling an insecure `TrustManager` use. */
private Guard getAnInsecureTrustManagerFlagGuard() {
result = any(InsecureTrustManagerFlag flag).getAFlag().asExpr()
}

/** Holds if `node` is guarded by a flag that suggests an intentionally insecure use. */
private predicate isNodeGuardedByFlag(DataFlow::Node node) {
exists(Guard g | g.controls(node.asExpr().getBasicBlock(), _) |
g = getASecurityFeatureFlagGuard() or g = getAnInsecureTrustManagerFlagGuard()
)
}

from
DataFlow::PathNode source, DataFlow::PathNode sink, InsecureTrustManagerConfiguration cfg,
RefType trustManager
where
cfg.hasFlowPath(source, sink) and
not isNodeGuardedByFlag(sink.getNode()) and
trustManager = source.getNode().asExpr().(ClassInstanceExpr).getConstructedType()
select sink, source, sink, "$@ that is defined $@ and trusts any certificate, is used here.",
source, "This trustmanager", trustManager, "here"
from DataFlow::PathNode source, DataFlow::PathNode sink
where any(InsecureTrustManagerConfiguration cfg).hasFlowPath(source, sink)
select sink, source, sink, "This $@, that is defined $@, trusts any certificate and is used here.",
source, "TrustManager", source.getNode().asExpr().(ClassInstanceExpr).getConstructedType(), "here"
Loading