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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
public class InsecureLdapAuth {
/** LDAP authentication */
public DirContext ldapAuth(String ldapUserName, String password) {
{
// BAD: LDAP authentication in cleartext
String ldapUrl = "ldap://ad.your-server.com:389";
}

{
// GOOD: LDAPS authentication over SSL
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");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
environment.put(Context.SECURITY_CREDENTIALS, password);
DirContext dirContext = new InitialDirContext(environment);
return dirContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!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 LDAPS to send credentials through SSL or use SASL authentication.</p>
</recommendation>

<example>
<p>The following example shows two ways of using LDAP authentication. In the 'BAD' case, the credentials are transmitted in cleartext. In the 'GOOD' case, the credentials are transmitted over SSL.</p>
<sample src="InsecureLDAPAuth.java" />
</example>

<references>
<li>
CWE:
<a href="https://cwe.mitre.org/data/definitions/522">CWE-522: Insufficiently Protected Credentials</a>
<a href="https://cwe.mitre.org/data/definitions/319">CWE-319: Cleartext Transmission of Sensitive Information</a>
</li>
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
<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 consists of sending the LDAP server the fully qualified DN of the client (user) and the client's clear-text password</a>
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
</li>
</references>
</qhelp>
153 changes: 153 additions & 0 deletions java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* @name Insecure LDAP authentication
* @description LDAP authentication with credentials sent in cleartext.
* @kind path-problem
* @id java/insecure-ldap-auth
* @tags security
* external/cwe-522
* external/cwe-319
*/

import java
import semmle.code.java.frameworks.Jndi
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph

/**
* Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
*/
private string getPrivateHostRegex() {
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
result =
"(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?"
}

/**
* String of LDAP connections not in private domains.
*/
class LdapStringLiteral extends StringLiteral {
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
LdapStringLiteral() {
// Match connection strings with the LDAP protocol and without private IP addresses to reduce false positives.
exists(string s | this.getRepresentedString() = s |
s.regexpMatch("(?i)ldap://[\\[a-zA-Z0-9].*") and
Comment thread
luchua-bc marked this conversation as resolved.
not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
)
}
}

/** 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") }
}

/**
* Holds if a non-private LDAP string is concatenated from both protocol and host.
*/
predicate concatLdapString(Expr protocol, Expr host) {
(
protocol.(CompileTimeConstantExpr).getStringValue().regexpMatch("(?i)ldap(://)?") or
protocol
.(VarAccess)
.getVariable()
.getAnAssignedValue()
.(CompileTimeConstantExpr)
.getStringValue()
.regexpMatch("(?i)ldap(://)?")
) and
not exists(string hostString |
hostString = host.(CompileTimeConstantExpr).getStringValue() or
hostString =
host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
|
hostString.length() = 0 or // Empty host is loopback address
hostString.regexpMatch(getPrivateHostRegex())
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
)
}

/** Gets the leftmost operand in a concatenated string */
Expr getLeftmostConcatOperand(Expr expr) {
if expr instanceof AddExpr
then result = getLeftmostConcatOperand(expr.(AddExpr).getLeftOperand())
else result = expr
}

/**
* String concatenated with `LdapStringLiteral`.
*/
class LdapString extends Expr {
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
LdapString() {
this instanceof LdapStringLiteral
or
concatLdapString(this.(AddExpr).getLeftOperand(),
getLeftmostConcatOperand(this.(AddExpr).getRightOperand()))
}
}

/**
* 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)`.
*/
Comment thread
luchua-bc marked this conversation as resolved.
predicate isProviderUrlEnv(MethodAccess ma) {
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
(
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
)
Comment thread
luchua-bc marked this conversation as resolved.
)
}

/**
* Holds if the value "simple" is passed to env `Hashtable` as the authentication mechanism, i.e.
* `env.put(Context.SECURITY_AUTHENTICATION, "simple")` or `env.setProperty(Context.SECURITY_AUTHENTICATION, "simple")`.
*/
Comment thread
luchua-bc marked this conversation as resolved.
predicate isSimpleAuthEnv(MethodAccess ma) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
(
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() =
"java.naming.security.authentication"
or
exists(Field f |
ma.getArgument(0) = f.getAnAccess() and
f.hasName("SECURITY_AUTHENTICATION") and
f.getDeclaringType() instanceof TypeNamingContext
)
) and
ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "simple"
}

/**
* A taint-tracking configuration for cleartext credentials in LDAP authentication.
*/
class LdapAuthFlowConfig extends TaintTracking::Configuration {
LdapAuthFlowConfig() { this = "InsecureLdapAuth:LdapAuthFlowConfig" }

/** Source of non-private LDAP connection string */
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof LdapString }

/** Sink of provider URL with simple authentication */
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess pma |
sink.asExpr() = pma.getArgument(1) and
isProviderUrlEnv(pma) and
exists(MethodAccess sma |
sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
isSimpleAuthEnv(sma)
)
)
}
}

from DataFlow::PathNode source, DataFlow::PathNode sink, LdapAuthFlowConfig config
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
"LDAP connection string"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
edges
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl |
nodes
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | semmle.label | ldapUrl |
#select
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:25:20:25:39 | ... + ... | LDAP connection string |
| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.ldap.InitialLdapContext;

public class InsecureLdapAuth {
// BAD - Test LDAP authentication in cleartext using `DirContext`.
public void testCleartextLdapAuth(String ldapUserName, String password) {
Comment thread
luchua-bc marked this conversation as resolved.
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);
}

// BAD - Test LDAP authentication in cleartext using `DirContext`.
public void testCleartextLdapAuth(String ldapUserName, String password, String serverName) {
String ldapUrl = "ldap://"+serverName+":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);
}

// GOOD - Test LDAP authentication over SSL.
public void testSslLdapAuth(String ldapUserName, String password) {
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);
}

// GOOD - Test LDAP authentication with SASL authentication.
public void testSaslLdapAuth(String ldapUserName, String password) {
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);
}

// GOOD - Test LDAP authentication in cleartext connecting to local LDAP server.
public void testCleartextLdapAuth2(String ldapUserName, String password) {
String ldapUrl = "ldap://localhost: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);
}

// BAD - Test LDAP authentication in cleartext using `InitialLdapContext`.
public void testCleartextLdapAuth3(String ldapUserName, String password) {
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);
InitialLdapContext ldapContext = new InitialLdapContext(environment, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql