Skip to content

Commit 4ec78d0

Browse files
committed
Insecure LDAP authentication
1 parent 49f902d commit 4ec78d0

File tree

6 files changed

+317
-0
lines changed

6 files changed

+317
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
public class InsecureLdapAuth {
2+
/** LDAP authentication */
3+
public DirContext ldapAuth(String ldapUserName, String password) {
4+
{
5+
// BAD: LDAP authentication in cleartext
6+
String ldapUrl = "ldap://ad.your-server.com:389";
7+
}
8+
9+
{
10+
// GOOD: LDAPS authentication over SSL
11+
String ldapUrl = "ldaps://ad.your-server.com:636";
12+
}
13+
14+
Hashtable<String, String> environment = new Hashtable<String, String>();
15+
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
16+
environment.put(Context.PROVIDER_URL, ldapUrl);
17+
environment.put(Context.REFERRAL, "follow");
18+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
19+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
20+
environment.put(Context.SECURITY_CREDENTIALS, password);
21+
DirContext dirContext = new InitialDirContext(environment);
22+
return dirContext;
23+
}
24+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<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>
6+
</overview>
7+
8+
<recommendation>
9+
<p>Use LDAPS to send credentials through SSL or use SASL authentication.</p>
10+
</recommendation>
11+
12+
<example>
13+
<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>
14+
<sample src="InsecureLDAPAuth.java" />
15+
</example>
16+
17+
<references>
18+
<li>
19+
CWE:
20+
<a href="https://cwe.mitre.org/data/definitions/522">CWE-522: Insufficiently Protected Credentials</a>
21+
<a href="https://cwe.mitre.org/data/definitions/319">CWE-319: Cleartext Transmission of Sensitive Information</a>
22+
</li>
23+
<li>
24+
Oracle:
25+
<a href="https://docs.oracle.com/javase/jndi/tutorial/ldap/misc/url.html">LDAP and LDAPS URLs</a>
26+
</li>
27+
<li>
28+
Oracle:
29+
<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>
30+
</li>
31+
</references>
32+
</qhelp>
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* @name Insecure LDAP authentication
3+
* @description LDAP authentication with credentials sent in cleartext.
4+
* @kind path-problem
5+
* @id java/insecure-ldap-auth
6+
* @tags security
7+
* external/cwe-522
8+
* external/cwe-319
9+
*/
10+
11+
import java
12+
import semmle.code.java.frameworks.Jndi
13+
import semmle.code.java.dataflow.TaintTracking
14+
import DataFlow::PathGraph
15+
16+
/**
17+
* Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
18+
*/
19+
private string getPrivateHostRegex() {
20+
result =
21+
"(?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\\]?(?:[:/?#].*)?"
22+
}
23+
24+
/**
25+
* String of LDAP connections not in private domains.
26+
*/
27+
class LdapStringLiteral extends StringLiteral {
28+
LdapStringLiteral() {
29+
// Match connection strings with the LDAP protocol and without private IP addresses to reduce false positives.
30+
exists(string s | this.getRepresentedString() = s |
31+
s.regexpMatch("(?i)ldap://[\\[a-zA-Z0-9].*") and
32+
not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
33+
)
34+
}
35+
}
36+
37+
/** The interface `javax.naming.Context`. */
38+
class TypeNamingContext extends Interface {
39+
TypeNamingContext() { this.hasQualifiedName("javax.naming", "Context") }
40+
}
41+
42+
/** The class `java.util.Hashtable`. */
43+
class TypeHashtable extends Class {
44+
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
45+
}
46+
47+
/**
48+
* Holds if a non-private LDAP string is concatenated from both protocol and host.
49+
*/
50+
predicate concatLdapString(Expr protocol, Expr host) {
51+
(
52+
protocol.(CompileTimeConstantExpr).getStringValue().regexpMatch("(?i)ldap(://)?") or
53+
protocol
54+
.(VarAccess)
55+
.getVariable()
56+
.getAnAssignedValue()
57+
.(CompileTimeConstantExpr)
58+
.getStringValue()
59+
.regexpMatch("(?i)ldap(://)?")
60+
) and
61+
not exists(string hostString |
62+
hostString = host.(CompileTimeConstantExpr).getStringValue() or
63+
hostString =
64+
host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
65+
|
66+
hostString.length() = 0 or // Empty host is loopback address
67+
hostString.regexpMatch(getPrivateHostRegex())
68+
)
69+
}
70+
71+
/** Gets the leftmost operand in a concatenated string */
72+
Expr getLeftmostConcatOperand(Expr expr) {
73+
if expr instanceof AddExpr
74+
then result = getLeftmostConcatOperand(expr.(AddExpr).getLeftOperand())
75+
else result = expr
76+
}
77+
78+
/**
79+
* String concatenated with `LdapStringLiteral`.
80+
*/
81+
class LdapString extends Expr {
82+
LdapString() {
83+
this instanceof LdapStringLiteral
84+
or
85+
concatLdapString(this.(AddExpr).getLeftOperand(),
86+
getLeftmostConcatOperand(this.(AddExpr).getRightOperand()))
87+
}
88+
}
89+
90+
/**
91+
* Tainted value passed to env `Hashtable` as the provider URL, i.e.
92+
* `env.put(Context.PROVIDER_URL, tainted)` or `env.setProperty(Context.PROVIDER_URL, tainted)`.
93+
*/
94+
predicate isProviderUrlEnv(MethodAccess ma) {
95+
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
96+
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
97+
(
98+
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url"
99+
or
100+
exists(Field f |
101+
ma.getArgument(0) = f.getAnAccess() and
102+
f.hasName("PROVIDER_URL") and
103+
f.getDeclaringType() instanceof TypeNamingContext
104+
)
105+
)
106+
}
107+
108+
/**
109+
* Holds if the value "simple" is passed to env `Hashtable` as the authentication mechanism, i.e.
110+
* `env.put(Context.SECURITY_AUTHENTICATION, "simple")` or `env.setProperty(Context.SECURITY_AUTHENTICATION, "simple")`.
111+
*/
112+
predicate isSimpleAuthEnv(MethodAccess ma) {
113+
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
114+
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
115+
(
116+
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() =
117+
"java.naming.security.authentication"
118+
or
119+
exists(Field f |
120+
ma.getArgument(0) = f.getAnAccess() and
121+
f.hasName("SECURITY_AUTHENTICATION") and
122+
f.getDeclaringType() instanceof TypeNamingContext
123+
)
124+
) and
125+
ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "simple"
126+
}
127+
128+
/**
129+
* A taint-tracking configuration for cleartext credentials in LDAP authentication.
130+
*/
131+
class LdapAuthFlowConfig extends TaintTracking::Configuration {
132+
LdapAuthFlowConfig() { this = "InsecureLdapAuth:LdapAuthFlowConfig" }
133+
134+
/** Source of non-private LDAP connection string */
135+
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof LdapString }
136+
137+
/** Sink of provider URL with simple authentication */
138+
override predicate isSink(DataFlow::Node sink) {
139+
exists(MethodAccess pma |
140+
sink.asExpr() = pma.getArgument(1) and
141+
isProviderUrlEnv(pma) and
142+
exists(MethodAccess sma |
143+
sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
144+
isSimpleAuthEnv(sma)
145+
)
146+
)
147+
}
148+
}
149+
150+
from DataFlow::PathNode source, DataFlow::PathNode sink, LdapAuthFlowConfig config
151+
where config.hasFlowPath(source, sink)
152+
select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
153+
"LDAP connection string"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
edges
2+
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
3+
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
4+
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl |
5+
nodes
6+
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
7+
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
8+
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
9+
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
10+
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
11+
| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | semmle.label | ldapUrl |
12+
#select
13+
| 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 |
14+
| 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 |
15+
| 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 |
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import java.util.Hashtable;
2+
3+
import javax.naming.Context;
4+
import javax.naming.directory.DirContext;
5+
import javax.naming.directory.InitialDirContext;
6+
import javax.naming.ldap.InitialLdapContext;
7+
8+
public class InsecureLdapAuth {
9+
// BAD - Test LDAP authentication in cleartext using `DirContext`.
10+
public void testCleartextLdapAuth(String ldapUserName, String password) {
11+
String ldapUrl = "ldap://ad.your-server.com:389";
12+
Hashtable<String, String> environment = new Hashtable<String, String>();
13+
environment.put(Context.INITIAL_CONTEXT_FACTORY,
14+
"com.sun.jndi.ldap.LdapCtxFactory");
15+
environment.put(Context.PROVIDER_URL, ldapUrl);
16+
environment.put(Context.REFERRAL, "follow");
17+
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
18+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
19+
environment.put(Context.SECURITY_CREDENTIALS, password);
20+
DirContext dirContext = new InitialDirContext(environment);
21+
}
22+
23+
// BAD - Test LDAP authentication in cleartext using `DirContext`.
24+
public void testCleartextLdapAuth(String ldapUserName, String password, String serverName) {
25+
String ldapUrl = "ldap://"+serverName+":389";
26+
Hashtable<String, String> environment = new Hashtable<String, String>();
27+
environment.put(Context.INITIAL_CONTEXT_FACTORY,
28+
"com.sun.jndi.ldap.LdapCtxFactory");
29+
environment.put(Context.PROVIDER_URL, ldapUrl);
30+
environment.put(Context.REFERRAL, "follow");
31+
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
32+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
33+
environment.put(Context.SECURITY_CREDENTIALS, password);
34+
DirContext dirContext = new InitialDirContext(environment);
35+
}
36+
37+
// GOOD - Test LDAP authentication over SSL.
38+
public void testSslLdapAuth(String ldapUserName, String password) {
39+
String ldapUrl = "ldaps://ad.your-server.com:636";
40+
Hashtable<String, String> environment = new Hashtable<String, String>();
41+
environment.put(Context.INITIAL_CONTEXT_FACTORY,
42+
"com.sun.jndi.ldap.LdapCtxFactory");
43+
environment.put(Context.PROVIDER_URL, ldapUrl);
44+
environment.put(Context.REFERRAL, "follow");
45+
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
46+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
47+
environment.put(Context.SECURITY_CREDENTIALS, password);
48+
DirContext dirContext = new InitialDirContext(environment);
49+
}
50+
51+
// GOOD - Test LDAP authentication with SASL authentication.
52+
public void testSaslLdapAuth(String ldapUserName, String password) {
53+
String ldapUrl = "ldap://ad.your-server.com:389";
54+
Hashtable<String, String> environment = new Hashtable<String, String>();
55+
environment.put(Context.INITIAL_CONTEXT_FACTORY,
56+
"com.sun.jndi.ldap.LdapCtxFactory");
57+
environment.put(Context.PROVIDER_URL, ldapUrl);
58+
environment.put(Context.REFERRAL, "follow");
59+
environment.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");
60+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
61+
environment.put(Context.SECURITY_CREDENTIALS, password);
62+
DirContext dirContext = new InitialDirContext(environment);
63+
}
64+
65+
// GOOD - Test LDAP authentication in cleartext connecting to local LDAP server.
66+
public void testCleartextLdapAuth2(String ldapUserName, String password) {
67+
String ldapUrl = "ldap://localhost:389";
68+
Hashtable<String, String> environment = new Hashtable<String, String>();
69+
environment.put(Context.INITIAL_CONTEXT_FACTORY,
70+
"com.sun.jndi.ldap.LdapCtxFactory");
71+
environment.put(Context.PROVIDER_URL, ldapUrl);
72+
environment.put(Context.REFERRAL, "follow");
73+
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
74+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
75+
environment.put(Context.SECURITY_CREDENTIALS, password);
76+
DirContext dirContext = new InitialDirContext(environment);
77+
}
78+
79+
// BAD - Test LDAP authentication in cleartext using `InitialLdapContext`.
80+
public void testCleartextLdapAuth3(String ldapUserName, String password) {
81+
String ldapUrl = "ldap://ad.your-server.com:389";
82+
Hashtable<String, String> environment = new Hashtable<String, String>();
83+
environment.put(Context.INITIAL_CONTEXT_FACTORY,
84+
"com.sun.jndi.ldap.LdapCtxFactory");
85+
environment.put(Context.PROVIDER_URL, ldapUrl);
86+
environment.put(Context.REFERRAL, "follow");
87+
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
88+
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
89+
environment.put(Context.SECURITY_CREDENTIALS, password);
90+
InitialLdapContext ldapContext = new InitialLdapContext(environment, null);
91+
}
92+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql

0 commit comments

Comments
 (0)