Skip to content

Java: Promote LDAP Authentication Query #12458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
57886e1
Moved files from experimental to src/
egregius313 Mar 2, 2023
5ff4fcb
Replace `exists` with `any`
egregius313 Mar 2, 2023
938d953
Refactor getLeftmostOperand method
egregius313 Mar 3, 2023
9275b54
Refactoring the InsecureLdapUrl constructor
egregius313 Mar 7, 2023
3936aea
Split Ldap query file into libraries
egregius313 Mar 9, 2023
98b445c
Convert test to InlineExpectationsTest
egregius313 Mar 9, 2023
05da1dc
Merge concatInsecureLdapString into InsecureLdapUrl constructor
egregius313 Mar 9, 2023
6a0167f
Convert to using the new DataFlow modules
egregius313 Mar 9, 2023
db60c08
Add security severity
egregius313 Mar 9, 2023
0f4709e
Add change note
egregius313 Mar 9, 2023
59ce0d7
Documentation changes
egregius313 Mar 9, 2023
efdfc2d
Change version of PathNode used to appropriate module
egregius313 Mar 9, 2023
752620a
Rename SSL configuration and fix PathGraph
egregius313 Mar 9, 2023
cb58936
Documentation changes
egregius313 Mar 10, 2023
658c54a
Change names of configuration to fit new naming convention
egregius313 Mar 10, 2023
151357d
Make classes/predicates not used outside of query private
egregius313 Mar 17, 2023
24d4859
Import changes
egregius313 Mar 17, 2023
f28f1af
Add `InsecureLdapUrlSink`
egregius313 Mar 17, 2023
0eaf222
Move public classes/predicates to top of library file
egregius313 Mar 17, 2023
43d79dc
Apply docs review suggestions
egregius313 Mar 24, 2023
106e5e7
Docs review suggestion
egregius313 Mar 24, 2023
9bfb13b
Update to the `Global`/`flow*` api
egregius313 Mar 27, 2023
97ec808
Make configuration public
egregius313 Mar 28, 2023
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
128 changes: 128 additions & 0 deletions java/ql/lib/semmle/code/java/security/InsecureLdapAuth.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/** Provides classes to reason about insecure LDAP authentication. */

import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.frameworks.Networking
private import semmle.code.java.frameworks.Jndi

/**
* An expression that represents an insecure (non-SSL, non-private) LDAP URL.
*/
class InsecureLdapUrl extends Expr {
InsecureLdapUrl() {
this instanceof InsecureLdapUrlLiteral
or
// Concatentation of insecure protcol and non-private host:
// protocol + host + ...
exists(AddExpr e, CompileTimeConstantExpr protocol, Expr rest, Expr host |
e = this and
protocol = e.getLeftOperand() and
rest = e.getRightOperand() and
if rest instanceof AddExpr then host = rest.(AddExpr).getLeftOperand() else host = rest
|
protocol.getStringValue() = "ldap://" and
not exists(string hostString | hostString = getHostname(host) |
hostString.length() = 0 or // Empty host is loopback address
hostString instanceof PrivateHostName
)
)
}
}

/**
* A sink representing the construction of a `DirContextEnvironment`.
*/
class InsecureLdapUrlSink extends DataFlow::Node {
InsecureLdapUrlSink() {
exists(ConstructorCall cc |
cc.getConstructedType().getAnAncestor() instanceof TypeDirContext and
this.asExpr() = cc.getArgument(0)
)
}
}

/**
* Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
*/
predicate isBasicAuthEnv(MethodAccess ma) {
hasFieldValueEnv(ma, "java.naming.security.authentication", "simple") or
hasFieldNameEnv(ma, "SECURITY_AUTHENTICATION", "simple")
}

/**
* Holds if `ma` sets `java.naming.security.protocol` (also known as `Context.SECURITY_PROTOCOL`) to `ssl` in some `Hashtable`.
*/
predicate isSslEnv(MethodAccess ma) {
hasFieldValueEnv(ma, "java.naming.security.protocol", "ssl") or
hasFieldNameEnv(ma, "SECURITY_PROTOCOL", "ssl")
}

/**
* Holds if `ma` writes the `java.naming.provider.url` (also known as `Context.PROVIDER_URL`) key of a `Hashtable`.
*/
predicate isProviderUrlSetter(MethodAccess ma) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
ma.getMethod().hasName(["put", "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
)
)
}

/**
* An insecure (non-SSL, non-private) LDAP URL string literal.
*/
private class InsecureLdapUrlLiteral extends StringLiteral {
InsecureLdapUrlLiteral() {
// Match connection strings with the LDAP protocol and without private IP addresses to reduce false positives.
exists(string s | this.getValue() = s |
s.regexpMatch("(?i)ldap://[\\[a-zA-Z0-9].*") and
not s.substring(7, s.length()) instanceof PrivateHostName
)
}
}

/** The class `java.util.Hashtable`. */
private class TypeHashtable extends Class {
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
}

/** Get the string value of an expression representing a hostname. */
private string getHostname(Expr expr) {
result = expr.(CompileTimeConstantExpr).getStringValue() or
result =
expr.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
}

/**
* Holds if `ma` sets `fieldValue` to `envValue` in some `Hashtable`.
*/
bindingset[fieldValue, envValue]
private predicate hasFieldValueEnv(MethodAccess ma, string fieldValue, string envValue) {
// environment.put("java.naming.security.authentication", "simple")
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
ma.getMethod().hasName(["put", "setProperty"]) and
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = fieldValue and
ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = envValue
}

/**
* Holds if `ma` sets attribute name `fieldName` to `envValue` in some `Hashtable`.
*/
bindingset[fieldName, envValue]
private predicate hasFieldNameEnv(MethodAccess ma, string fieldName, string envValue) {
// environment.put(Context.SECURITY_AUTHENTICATION, "simple")
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
ma.getMethod().hasName(["put", "setProperty"]) and
exists(Field f |
ma.getArgument(0) = f.getAnAccess() and
f.hasName(fieldName) and
f.getDeclaringType() instanceof TypeNamingContext
) and
ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = envValue
}
59 changes: 59 additions & 0 deletions java/ql/lib/semmle/code/java/security/InsecureLdapAuthQuery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** Provides dataflow configurations to reason about insecure LDAP authentication. */

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.frameworks.Jndi
import semmle.code.java.security.InsecureLdapAuth

/**
* A taint-tracking configuration for `ldap://` URL in LDAP authentication.
*/
module InsecureLdapUrlConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof InsecureLdapUrl }

predicate isSink(DataFlow::Node sink) { sink instanceof InsecureLdapUrlSink }

/** Method call of `env.put()`. */
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(MethodAccess ma |
pred.asExpr() = ma.getArgument(1) and
isProviderUrlSetter(ma) and
succ.asExpr() = ma.getQualifier()
)
}
}

module InsecureLdapUrlFlow = TaintTracking::Global<InsecureLdapUrlConfig>;

/**
* A taint-tracking configuration for `simple` basic-authentication in LDAP configuration.
*/
private module BasicAuthConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
exists(MethodAccess ma |
isBasicAuthEnv(ma) and
ma.getQualifier() = src.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
)
}

predicate isSink(DataFlow::Node sink) { sink instanceof InsecureLdapUrlSink }
}

module BasicAuthFlow = DataFlow::Global<BasicAuthConfig>;

/**
* A taint-tracking configuration for `ssl` configuration in LDAP authentication.
*/
private module RequiresSslConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
exists(MethodAccess ma |
isSslEnv(ma) and
ma.getQualifier() = src.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
)
}

predicate isSink(DataFlow::Node sink) { sink instanceof InsecureLdapUrlSink }
}

module RequiresSslFlow = DataFlow::Global<RequiresSslConfig>;
51 changes: 51 additions & 0 deletions java/ql/src/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>

<overview>
<p>
When using the Java LDAP API to perform LDAPv3-style extended operations
and controls, a context with connection properties including user
credentials is started. Transmission of LDAP credentials in cleartext
allows remote attackers to obtain sensitive information by sniffing the
network.
</p>
</overview>

<recommendation>
<p>
Use the <code>ldaps://</code> protocol to send credentials through SSL or
use SASL authentication.
</p>
</recommendation>

<example>
<p>
In the following (bad) example, a <code>ldap://</code> URL is used and
credentials will be sent in plaintext.
</p>
<sample src="LdapAuthUseLdap.java"/>

<p>
In the following (good) example, a <code>ldaps://</code> URL is used so
credentials will be encrypted with SSL.
</p>
<sample src="LdapAuthUseLdaps.java"/>

<p>
In the following (good) example, a <code>ldap://</code> URL is used, but
SASL authentication is enabled so that the credentials will be encrypted.
</p>
<sample src="LdapEnableSasl.java"/>
</example>

<references>
<li>
Oracle:
<a href="https://docs.oracle.com/javase/jndi/tutorial/ldap/misc/url.html">LDAP and LDAPS URLs</a>
</li>
<li>
Oracle:
<a href="https://docs.oracle.com/javase/tutorial/jndi/ldap/simple.html">Simple authentication</a>
</li>
</references>
</qhelp>
25 changes: 25 additions & 0 deletions java/ql/src/Security/CWE/CWE-522/InsecureLdapAuth.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @name Insecure LDAP authentication
* @description LDAP authentication with credentials sent in cleartext makes sensitive information vulnerable to remote attackers
* @kind path-problem
* @problem.severity warning
* @security-severity 8.8
* @precision medium
* @id java/insecure-ldap-auth
* @tags security
* experimental
* external/cwe/cwe-522
* external/cwe/cwe-319
*/

import java
import semmle.code.java.security.InsecureLdapAuthQuery
import InsecureLdapUrlFlow::PathGraph

from InsecureLdapUrlFlow::PathNode source, InsecureLdapUrlFlow::PathNode sink
where
InsecureLdapUrlFlow::flowPath(source, sink) and
BasicAuthFlow::flowTo(sink.getNode()) and
not RequiresSslFlow::flowTo(sink.getNode())
select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
"LDAP connection string"
9 changes: 9 additions & 0 deletions java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
String ldapUrl = "ldap://ad.your-server.com:389";
Hashtable<String, String> environment = new Hashtable<String, String>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapUrl);
environment.put(Context.REFERRAL, "follow");
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
environment.put(Context.SECURITY_CREDENTIALS, password);
DirContext dirContext = new InitialDirContext(environment);
9 changes: 9 additions & 0 deletions java/ql/src/Security/CWE/CWE-522/LdapAuthUseLdaps.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
String ldapUrl = "ldaps://ad.your-server.com:636";
Hashtable<String, String> environment = new Hashtable<String, String>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapUrl);
environment.put(Context.REFERRAL, "follow");
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
environment.put(Context.SECURITY_CREDENTIALS, password);
DirContext dirContext = new InitialDirContext(environment);
9 changes: 9 additions & 0 deletions java/ql/src/Security/CWE/CWE-522/LdapEnableSasl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
String ldapUrl = "ldap://ad.your-server.com:389";
Hashtable<String, String> environment = new Hashtable<String, String>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapUrl);
environment.put(Context.REFERRAL, "follow");
environment.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
environment.put(Context.SECURITY_CREDENTIALS, password);
DirContext dirContext = new InitialDirContext(environment);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: newQuery
---
* The query `java/insecure-ldap-auth` has been promoted from experimental to the main query pack. This query detects transmission of cleartext credentials in LDAP authentication. Insecure LDAP authentication causes sensitive information to be vulnerable to remote attackers. This query was originally [submitted as an experimental query by @luchua-bc](https://github.com/github/codeql/pull/4854)

This file was deleted.

This file was deleted.

Loading