Merge pull request #5931 from atorralba/atorralba/promote-jndi-injection

Java: Promote JNDI Injection query from experimental
This commit is contained in:
Anders Schack-Mulligen
2021-08-04 15:48:44 +02:00
committed by GitHub
25 changed files with 725 additions and 691 deletions

View File

@@ -11,7 +11,7 @@ code execution.</p>
</overview>
<recommendation>
<p>The general recommendation is to not pass untrusted data to the <code>InitialContext.lookup
<p>The general recommendation is to avoid passing untrusted data to the <code>InitialContext.lookup
</code> method. If the name being used to look up the object must be provided by the user, make
sure that it's not in the form of an absolute URL or that it's the URL pointing to a trused server.
</p>

View File

@@ -1,6 +1,6 @@
/**
* @name JNDI lookup with user-controlled name
* @description Doing a JNDI lookup with user-controlled name can lead to download an untrusted
* @description Performing a JNDI lookup with a user-controlled name can lead to the download of an untrusted
* object and to execution of arbitrary code.
* @kind path-problem
* @problem.severity error
@@ -11,8 +11,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import JndiInjectionLib
import semmle.code.java.security.JndiInjectionQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, JndiInjectionFlowConfig conf

View File

@@ -1,261 +0,0 @@
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import experimental.semmle.code.java.frameworks.Jndi
import experimental.semmle.code.java.frameworks.spring.SpringJndi
import semmle.code.java.frameworks.SpringLdap
import experimental.semmle.code.java.frameworks.Shiro
/**
* A taint-tracking configuration for unvalidated user input that is used in JNDI lookup.
*/
class JndiInjectionFlowConfig extends TaintTracking::Configuration {
JndiInjectionFlowConfig() { this = "JndiInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof JndiInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
nameStep(node1, node2) or
jmxServiceUrlStep(node1, node2) or
jmxConnectorStep(node1, node2) or
rmiConnectorStep(node1, node2)
}
}
/** The class `java.util.Hashtable`. */
class TypeHashtable extends Class {
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
}
/** The class `javax.naming.directory.SearchControls`. */
class TypeSearchControls extends Class {
TypeSearchControls() { this.hasQualifiedName("javax.naming.directory", "SearchControls") }
}
/**
* The interface `org.springframework.ldap.core.LdapOperations` (spring-ldap 1.2.x and newer) or
* `org.springframework.ldap.LdapOperations` (spring-ldap 1.1.x).
*/
class TypeSpringLdapOperations extends Interface {
TypeSpringLdapOperations() {
this.hasQualifiedName("org.springframework.ldap.core", "LdapOperations") or
this.hasQualifiedName("org.springframework.ldap", "LdapOperations")
}
}
/**
* The interface `org.springframework.ldap.core.ContextMapper` (spring-ldap 1.2.x and newer) or
* `org.springframework.ldap.ContextMapper` (spring-ldap 1.1.x).
*/
class TypeSpringContextMapper extends Interface {
TypeSpringContextMapper() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.ldap.core", "ContextMapper") or
this.getSourceDeclaration().hasQualifiedName("org.springframework.ldap", "ContextMapper")
}
}
/** The interface `javax.management.remote.JMXConnector`. */
class TypeJMXConnector extends Interface {
TypeJMXConnector() { this.hasQualifiedName("javax.management.remote", "JMXConnector") }
}
/** The class `javax.management.remote.rmi.RMIConnector`. */
class TypeRMIConnector extends Class {
TypeRMIConnector() { this.hasQualifiedName("javax.management.remote.rmi", "RMIConnector") }
}
/** The class `javax.management.remote.JMXConnectorFactory`. */
class TypeJMXConnectorFactory extends Class {
TypeJMXConnectorFactory() {
this.hasQualifiedName("javax.management.remote", "JMXConnectorFactory")
}
}
/** The class `javax.management.remote.JMXServiceURL`. */
class TypeJMXServiceURL extends Class {
TypeJMXServiceURL() { this.hasQualifiedName("javax.management.remote", "JMXServiceURL") }
}
/** The interface `javax.naming.Context`. */
class TypeNamingContext extends Interface {
TypeNamingContext() { this.hasQualifiedName("javax.naming", "Context") }
}
/**
* JNDI sink for JNDI injection vulnerabilities, i.e. 1st argument to `lookup`, `lookupLink`,
* `doLookup`, `rename`, `list` or `listBindings` method from `InitialContext`.
*/
predicate jndiSinkMethod(Method m, int index) {
m.getDeclaringType().getAnAncestor() instanceof TypeInitialContext and
(
m.hasName("lookup") or
m.hasName("lookupLink") or
m.hasName("doLookup") or
m.hasName("rename") or
m.hasName("list") or
m.hasName("listBindings")
) and
index = 0
}
/**
* Spring sink for JNDI injection vulnerabilities, i.e. 1st argument to `lookup` method from
* Spring's `JndiTemplate`.
*/
predicate springJndiTemplateSinkMethod(Method m, int index) {
m.getDeclaringType() instanceof TypeSpringJndiTemplate and
m.hasName("lookup") and
index = 0
}
/**
* Spring sink for JNDI injection vulnerabilities, i.e. 1st argument to `lookup`, `lookupContext`,
* `findByDn`, `rename`, `list`, `listBindings`, `unbind`, `search` or `searchForObject` method
* from Spring's `LdapOperations`.
*/
predicate springLdapTemplateSinkMethod(MethodAccess ma, Method m, int index) {
m.getDeclaringType().getAnAncestor() instanceof TypeSpringLdapOperations and
(
m.hasName("lookup")
or
m.hasName("lookupContext")
or
m.hasName("findByDn")
or
m.hasName("rename")
or
m.hasName("list")
or
m.hasName("listBindings")
or
m.hasName("unbind") and ma.getArgument(1).(CompileTimeConstantExpr).getBooleanValue() = true
or
m.getName().matches("search%") and
m.getParameterType(m.getNumberOfParameters() - 1) instanceof TypeSpringContextMapper and
not m.getAParamType() instanceof TypeSearchControls
or
m.hasName("search") and ma.getArgument(3).(CompileTimeConstantExpr).getBooleanValue() = true
) and
index = 0
}
/**
* Apache Shiro sink for JNDI injection vulnerabilities, i.e. 1st argument to `lookup` method from
* Shiro's `JndiTemplate`.
*/
predicate shiroSinkMethod(Method m, int index) {
m.getDeclaringType() instanceof TypeShiroJndiTemplate and
m.hasName("lookup") and
index = 0
}
/**
* `JMXConnectorFactory` sink for JNDI injection vulnerabilities, i.e. 1st argument to `connect`
* method from `JMXConnectorFactory`.
*/
predicate jmxConnectorFactorySinkMethod(Method m, int index) {
m.getDeclaringType() instanceof TypeJMXConnectorFactory and
m.hasName("connect") and
index = 0
}
/**
* Tainted value passed to env `Hashtable` as the provider URL, i.e.
* `env.put(Context.PROVIDER_URL, tainted)` or `env.setProperty(Context.PROVIDER_URL, tainted)`.
*/
predicate providerUrlEnv(MethodAccess ma, Method m, int index) {
m.getDeclaringType().getAnAncestor() instanceof TypeHashtable and
(m.hasName("put") or m.hasName("setProperty")) and
(
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url"
or
exists(Field f |
ma.getArgument(0) = f.getAnAccess() and
f.hasName("PROVIDER_URL") and
f.getDeclaringType() instanceof TypeNamingContext
)
) and
index = 1
}
/** Holds if parameter at index `index` in method `m` is JNDI injection sink. */
predicate jndiInjectionSinkMethod(MethodAccess ma, Method m, int index) {
jndiSinkMethod(m, index) or
springJndiTemplateSinkMethod(m, index) or
springLdapTemplateSinkMethod(ma, m, index) or
shiroSinkMethod(m, index) or
jmxConnectorFactorySinkMethod(m, index) or
providerUrlEnv(ma, m, index)
}
/** A data flow sink for unvalidated user input that is used in JNDI lookup. */
class JndiInjectionSink extends DataFlow::ExprNode {
JndiInjectionSink() {
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
ma.getArgument(index) = this.getExpr() and
jndiInjectionSinkMethod(ma, m, index)
)
or
exists(MethodAccess ma, Method m |
ma.getMethod() = m and
ma.getQualifier() = this.getExpr() and
m.getDeclaringType().getAnAncestor() instanceof TypeJMXConnector and
m.hasName("connect")
)
}
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `CompositeName` or
* `CompoundName`, i.e. `new CompositeName(tainted)` or `new CompoundName(tainted)`.
*/
predicate nameStep(ExprNode n1, ExprNode n2) {
exists(ConstructorCall cc |
cc.getConstructedType() instanceof TypeCompositeName or
cc.getConstructedType() instanceof TypeCompoundName
|
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `JMXServiceURL`,
* i.e. `new JMXServiceURL(tainted)`.
*/
predicate jmxServiceUrlStep(ExprNode n1, ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeJMXServiceURL |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `JMXServiceURL` and
* `JMXConnector`, i.e. `JMXConnectorFactory.newJMXConnector(tainted)`.
*/
predicate jmxConnectorStep(ExprNode n1, ExprNode n2) {
exists(MethodAccess ma, Method m | n1.asExpr() = ma.getArgument(0) and n2.asExpr() = ma |
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeJMXConnectorFactory and
m.hasName("newJMXConnector")
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `JMXServiceURL` and
* `RMIConnector`, i.e. `new RMIConnector(tainted)`.
*/
predicate rmiConnectorStep(ExprNode n1, ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeRMIConnector |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}

View File

@@ -30,11 +30,6 @@ class InsecureLdapUrlLiteral extends StringLiteral {
}
}
/** The interface `javax.naming.Context`. */
class TypeNamingContext extends Interface {
TypeNamingContext() { this.hasQualifiedName("javax.naming", "Context") }
}
/** The class `java.util.Hashtable`. */
class TypeHashtable extends Class {
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }

View File

@@ -1,16 +0,0 @@
import java
/** The class `javax.naming.InitialContext`. */
class TypeInitialContext extends Class {
TypeInitialContext() { this.hasQualifiedName("javax.naming", "InitialContext") }
}
/** The class `javax.naming.CompositeName`. */
class TypeCompositeName extends Class {
TypeCompositeName() { this.hasQualifiedName("javax.naming", "CompositeName") }
}
/** The class `javax.naming.CompoundName`. */
class TypeCompoundName extends Class {
TypeCompoundName() { this.hasQualifiedName("javax.naming", "CompoundName") }
}

View File

@@ -1,6 +0,0 @@
import java
/** The class `org.apache.shiro.jndi.JndiTemplate`. */
class TypeShiroJndiTemplate extends Class {
TypeShiroJndiTemplate() { this.hasQualifiedName("org.apache.shiro.jndi", "JndiTemplate") }
}

View File

@@ -1,6 +0,0 @@
import java
/** The class `org.springframework.jndi.JndiTemplate`. */
class TypeSpringJndiTemplate extends Class {
TypeSpringJndiTemplate() { this.hasQualifiedName("org.springframework.jndi", "JndiTemplate") }
}

View File

@@ -86,3 +86,20 @@ class JMXRegistrationMethod extends Method {
)
}
}
/** The class `javax.management.remote.JMXConnectorFactory`. */
class TypeJMXConnectorFactory extends Class {
TypeJMXConnectorFactory() {
this.hasQualifiedName("javax.management.remote", "JMXConnectorFactory")
}
}
/** The class `javax.management.remote.JMXServiceURL`. */
class TypeJMXServiceURL extends Class {
TypeJMXServiceURL() { this.hasQualifiedName("javax.management.remote", "JMXServiceURL") }
}
/** The class `javax.management.remote.rmi.RMIConnector`. */
class TypeRMIConnector extends Class {
TypeRMIConnector() { this.hasQualifiedName("javax.management.remote.rmi", "RMIConnector") }
}

View File

@@ -99,6 +99,7 @@ private module Frameworks {
private import semmle.code.java.security.InformationLeak
private import semmle.code.java.security.GroovyInjection
private import semmle.code.java.security.JexlInjectionSinkModels
private import semmle.code.java.security.JndiInjection
private import semmle.code.java.security.LdapInjection
private import semmle.code.java.security.MvelInjection
private import semmle.code.java.security.OgnlInjection

View File

@@ -1,5 +1,5 @@
/**
* Provides classes and predicates for working with the Java JDBC API.
* Provides classes and predicates for working with the Java JNDI API.
*/
import java
@@ -7,11 +7,31 @@ import semmle.code.java.Type
import semmle.code.java.Member
/*--- Types ---*/
/** The interface `javax.naming.Context`. */
class TypeNamingContext extends Interface {
TypeNamingContext() { this.hasQualifiedName("javax.naming", "Context") }
}
/** The class `javax.naming.CompositeName`. */
class TypeCompositeName extends Class {
TypeCompositeName() { this.hasQualifiedName("javax.naming", "CompositeName") }
}
/** The class `javax.naming.CompoundName`. */
class TypeCompoundName extends Class {
TypeCompoundName() { this.hasQualifiedName("javax.naming", "CompoundName") }
}
/** The interface `javax.naming.directory.DirContext`. */
class TypeDirContext extends Interface {
TypeDirContext() { this.hasQualifiedName("javax.naming.directory", "DirContext") }
}
/** The class `javax.naming.directory.SearchControls` */
class TypeSearchControls extends Class {
TypeSearchControls() { this.hasQualifiedName("javax.naming.directory", "SearchControls") }
}
/** The class `javax.naming.ldap.LdapName`. */
class TypeLdapName extends Class {
TypeLdapName() { this.hasQualifiedName("javax.naming.ldap", "LdapName") }

View File

@@ -59,6 +59,17 @@ class TypeSpringLdapUtils extends Class {
TypeSpringLdapUtils() { this.hasQualifiedName("org.springframework.ldap.support", "LdapUtils") }
}
/**
* The interface `org.springframework.ldap.core.LdapOperations` or
* `org.springframework.ldap.LdapOperations`
*/
class TypeLdapOperations extends Interface {
TypeLdapOperations() {
this.hasQualifiedName(["org.springframework.ldap.core", "org.springframework.ldap"],
"LdapOperations")
}
}
/*--- Methods ---*/
/**
* A method with the name `authenticate` declared in

View File

@@ -0,0 +1,206 @@
/** Provides classes to reason about JNDI injection vulnerabilities. */
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.frameworks.Jndi
private import semmle.code.java.frameworks.SpringLdap
/** A data flow sink for unvalidated user input that is used in JNDI lookup. */
abstract class JndiInjectionSink extends DataFlow::Node { }
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to
* the `JndiInjectionFlowConfig` configuration.
*/
class JndiInjectionAdditionalTaintStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for the `JndiInjectionFlowConfig` configuration.
*/
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
/** A default sink representing methods susceptible to JNDI injection attacks. */
private class DefaultJndiInjectionSink extends JndiInjectionSink {
DefaultJndiInjectionSink() { sinkNode(this, "jndi-injection") }
}
/**
* A method that does a JNDI lookup when it receives a specific argument set to `true`.
*/
private class ConditionedJndiInjectionSink extends JndiInjectionSink, DataFlow::ExprNode {
ConditionedJndiInjectionSink() {
exists(MethodAccess ma, Method m |
ma.getMethod() = m and
ma.getArgument(0) = this.asExpr() and
m.getDeclaringType().getASourceSupertype*() instanceof TypeLdapOperations
|
m.hasName("search") and
ma.getArgument(3).(CompileTimeConstantExpr).getBooleanValue() = true
or
m.hasName("unbind") and
ma.getArgument(1).(CompileTimeConstantExpr).getBooleanValue() = true
)
}
}
/**
* Tainted value passed to env `Hashtable` as the provider URL by calling
* `env.put(Context.PROVIDER_URL, tainted)` or `env.setProperty(Context.PROVIDER_URL, tainted)`.
*/
private class ProviderUrlJndiInjectionSink extends JndiInjectionSink, DataFlow::ExprNode {
ProviderUrlJndiInjectionSink() {
exists(MethodAccess ma, Method m |
ma.getMethod() = m and
ma.getArgument(1) = this.getExpr()
|
m.getDeclaringType().getASourceSupertype*() instanceof TypeHashtable and
(m.hasName("put") or m.hasName("setProperty")) and
(
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url"
or
exists(Field f |
ma.getArgument(0) = f.getAnAccess() and
f.hasName("PROVIDER_URL") and
f.getDeclaringType() instanceof TypeNamingContext
)
)
)
}
}
/** CSV sink models representing methods susceptible to JNDI injection attacks. */
private class DefaultJndiInjectionSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
"javax.naming;Context;true;lookup;;;Argument[0];jndi-injection",
"javax.naming;Context;true;lookupLink;;;Argument[0];jndi-injection",
"javax.naming;Context;true;rename;;;Argument[0];jndi-injection",
"javax.naming;Context;true;list;;;Argument[0];jndi-injection",
"javax.naming;Context;true;listBindings;;;Argument[0];jndi-injection",
"javax.naming;InitialContext;true;doLookup;;;Argument[0];jndi-injection",
"javax.management.remote;JMXConnector;true;connect;;;Argument[-1];jndi-injection",
"javax.management.remote;JMXConnectorFactory;false;connect;;;Argument[0];jndi-injection",
// Spring
"org.springframework.jndi;JndiTemplate;false;lookup;;;Argument[0];jndi-injection",
// spring-ldap 1.2.x and newer
"org.springframework.ldap.core;LdapOperations;true;lookup;;;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;lookupContext;;;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;findByDn;;;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;rename;;;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;list;;;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;listBindings;;;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;search;(Name,String,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;search;(Name,String,int,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;search;(Name,String,int,String[],ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;search;(String,String,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;search;(String,String,int,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;search;(String,String,int,String[],ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;searchForObject;(Name,String,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap.core;LdapOperations;true;searchForObject;(String,String,ContextMapper);;Argument[0];jndi-injection",
// spring-ldap 1.1.x
"org.springframework.ldap;LdapOperations;true;lookup;;;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;lookupContext;;;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;findByDn;;;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;rename;;;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;list;;;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;listBindings;;;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;search;(Name,String,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;search;(Name,String,int,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;search;(Name,String,int,String[],ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;search;(String,String,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;search;(String,String,int,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;search;(String,String,int,String[],ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;searchForObject;(Name,String,ContextMapper);;Argument[0];jndi-injection",
"org.springframework.ldap;LdapOperations;true;searchForObject;(String,String,ContextMapper);;Argument[0];jndi-injection",
// Shiro
"org.apache.shiro.jndi;JndiTemplate;false;lookup;;;Argument[0];jndi-injection"
]
}
}
/** A set of additional taint steps to consider when taint tracking JNDI injection related data flows. */
private class DefaultJndiInjectionAdditionalTaintStep extends JndiInjectionAdditionalTaintStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
nameStep(node1, node2) or
nameAddStep(node1, node2) or
jmxServiceUrlStep(node1, node2) or
jmxConnectorStep(node1, node2) or
rmiConnectorStep(node1, node2)
}
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `CompositeName` or
* `CompoundName` by calling `new CompositeName(tainted)` or `new CompoundName(tainted)`.
*/
private predicate nameStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc |
cc.getConstructedType() instanceof TypeCompositeName or
cc.getConstructedType() instanceof TypeCompoundName
|
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `CompositeName` or
* `CompoundName` by calling `new CompositeName().add(tainted)` or `new CompoundName().add(tainted)`.
*/
private predicate nameAddStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(Method m, MethodAccess ma |
ma.getMethod() = m and
m.hasName("add") and
(
m.getDeclaringType() instanceof TypeCompositeName or
m.getDeclaringType() instanceof TypeCompoundName
)
|
n1.asExpr() = ma.getAnArgument() and
n2.asExpr() = ma
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `JMXServiceURL`
* by calling `new JMXServiceURL(tainted)`.
*/
private predicate jmxServiceUrlStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeJMXServiceURL |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `JMXServiceURL` and
* `JMXConnector` by calling `JMXConnectorFactory.newJMXConnector(tainted)`.
*/
private predicate jmxConnectorStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m | n1.asExpr() = ma.getArgument(0) and n2.asExpr() = ma |
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeJMXConnectorFactory and
m.hasName("newJMXConnector")
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `JMXServiceURL` and
* `RMIConnector` by calling `new RMIConnector(tainted)`.
*/
private predicate rmiConnectorStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeRMIConnector |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/** The class `java.util.Hashtable`. */
private class TypeHashtable extends Class {
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
}

View File

@@ -0,0 +1,97 @@
/** Provides taint tracking configurations to be used in JNDI injection queries. */
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.SpringLdap
import semmle.code.java.security.JndiInjection
/**
* A taint-tracking configuration for unvalidated user input that is used in JNDI lookup.
*/
class JndiInjectionFlowConfig extends TaintTracking::Configuration {
JndiInjectionFlowConfig() { this = "JndiInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof JndiInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(JndiInjectionAdditionalTaintStep c).step(node1, node2)
}
}
/**
* A method that does a JNDI lookup when it receives a `SearchControls` argument with `setReturningObjFlag` = `true`
*/
private class UnsafeSearchControlsSink extends JndiInjectionSink {
UnsafeSearchControlsSink() {
exists(UnsafeSearchControlsConf conf, MethodAccess ma |
conf.hasFlowTo(DataFlow::exprNode(ma.getAnArgument()))
|
this.asExpr() = ma.getArgument(0)
)
}
}
/**
* Find flows between a `SearchControls` object with `setReturningObjFlag` = `true`
* and an argument of an `LdapOperations.search` or `DirContext.search` call.
*/
private class UnsafeSearchControlsConf extends DataFlow2::Configuration {
UnsafeSearchControlsConf() { this = "UnsafeSearchControlsConf" }
override predicate isSource(DataFlow::Node source) { source instanceof UnsafeSearchControls }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeSearchControlsArgument }
}
/**
* An argument of type `SearchControls` of an `LdapOperations.search` or `DirContext.search` call.
*/
private class UnsafeSearchControlsArgument extends DataFlow::ExprNode {
UnsafeSearchControlsArgument() {
exists(MethodAccess ma, Method m |
ma.getMethod() = m and
ma.getAnArgument() = this.asExpr() and
this.asExpr().getType() instanceof TypeSearchControls and
m.hasName("search")
|
m.getDeclaringType().getASourceSupertype*() instanceof TypeLdapOperations or
m.getDeclaringType().getASourceSupertype*() instanceof TypeDirContext
)
}
}
/**
* A `SearchControls` object with `setReturningObjFlag` = `true`.
*/
private class UnsafeSearchControls extends DataFlow::ExprNode {
UnsafeSearchControls() {
exists(MethodAccess ma |
ma.getMethod() instanceof SetReturningObjFlagMethod and
ma.getArgument(0).(CompileTimeConstantExpr).getBooleanValue() = true and
this.asExpr() = ma.getQualifier()
)
or
exists(ConstructorCall cc |
cc.getConstructedType() instanceof TypeSearchControls and
cc.getArgument(4).(CompileTimeConstantExpr).getBooleanValue() = true and
this.asExpr() = cc
)
}
}
/**
* The method `SearchControls.setReturningObjFlag`.
*/
private class SetReturningObjFlagMethod extends Method {
SetReturningObjFlagMethod() {
this.getDeclaringType() instanceof TypeSearchControls and
this.hasName("setReturningObjFlag")
}
}