Skip to content

Commit 9a56601

Browse files
authored
Merge pull request github#5164 from luchua-bc/java/insecure-ldap-endpoint
Java: CWE-297 Query to detect insecure LDAP endpoint configuration
2 parents da08c6e + 57953c5 commit 9a56601

File tree

7 files changed

+294
-0
lines changed

7 files changed

+294
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
public class InsecureLdapEndpoint {
2+
public Hashtable<String, String> createConnectionEnv() {
3+
Hashtable<String, String> env = new Hashtable<String, String>();
4+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
5+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
6+
7+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
8+
env.put(Context.SECURITY_PRINCIPAL, "username");
9+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
10+
11+
// BAD - Test configuration with disabled SSL endpoint check.
12+
{
13+
System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
14+
}
15+
16+
return env;
17+
}
18+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>Java versions 8u181 or greater have enabled LDAPS endpoint identification by default. Nowadays
8+
infrastructure services like LDAP are commonly deployed behind load balancers therefore the LDAP
9+
server name can be different from the FQDN of the LDAPS endpoint. If a service certificate does not
10+
properly contain a matching DNS name as part of the certificate, Java will reject it by default.</p>
11+
<p>Instead of addressing the issue properly by having a compliant certificate deployed, frequently
12+
developers simply disable the LDAPS endpoint check.</p>
13+
<p>Failing to validate the certificate makes the SSL session susceptible to a man-in-the-middle attack.
14+
This query checks whether the LDAPS endpoint check is disabled in system properties.</p>
15+
</overview>
16+
17+
<recommendation>
18+
<p>Replace any non-conforming LDAP server certificates to include a DNS name in the subjectAltName field
19+
of the certificate that matches the FQDN of the service.</p>
20+
</recommendation>
21+
22+
<example>
23+
<p>The following two examples show two ways of configuring LDAPS endpoint. In the 'BAD' case,
24+
endpoint check is disabled. In the 'GOOD' case, endpoint check is left enabled through the
25+
default Java configuration.</p>
26+
<sample src="InsecureLdapEndpoint.java" />
27+
<sample src="InsecureLdapEndpoint2.java" />
28+
</example>
29+
30+
<references>
31+
<li>
32+
Oracle Java 8 Update 181 (8u181):
33+
<a href="https://www.java.com/en/download/help/release_changes.html">Endpoint identification enabled on LDAPS connections</a>
34+
</li>
35+
<li>
36+
IBM:
37+
<a href="https://www.ibm.com/support/pages/how-do-i-fix-ldap-ssl-error-%E2%80%9Cjavasecuritycertcertificateexception-no-subject-alternative-names-present%E2%80%9D-websphere-application-server">Fix this LDAP SSL error</a>
38+
</li>
39+
</references>
40+
</qhelp>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @name Insecure LDAPS Endpoint Configuration
3+
* @description Java application configured to disable LDAPS endpoint identification does not validate
4+
* the SSL certificate to properly ensure that it is actually associated with that host.
5+
* @kind problem
6+
* @id java/insecure-ldaps-endpoint
7+
* @tags security
8+
* external/cwe-297
9+
*/
10+
11+
import java
12+
13+
/** The method to set a system property. */
14+
class SetSystemPropertyMethod extends Method {
15+
SetSystemPropertyMethod() {
16+
this.hasName("setProperty") and
17+
this.getDeclaringType().hasQualifiedName("java.lang", "System")
18+
}
19+
}
20+
21+
/** The class `java.util.Hashtable`. */
22+
class TypeHashtable extends Class {
23+
TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
24+
}
25+
26+
/**
27+
* The method to set Java properties either through `setProperty` declared in the class `Properties`
28+
* or `put` declared in its parent class `HashTable`.
29+
*/
30+
class SetPropertyMethod extends Method {
31+
SetPropertyMethod() {
32+
this.getDeclaringType().getAnAncestor() instanceof TypeHashtable and
33+
this.hasName(["put", "setProperty"])
34+
}
35+
}
36+
37+
/** The `setProperties` method declared in `java.lang.System`. */
38+
class SetSystemPropertiesMethod extends Method {
39+
SetSystemPropertiesMethod() {
40+
this.hasName("setProperties") and
41+
this.getDeclaringType().hasQualifiedName("java.lang", "System")
42+
}
43+
}
44+
45+
/**
46+
* Holds if `Expr` expr is evaluated to the string literal
47+
* `com.sun.jndi.ldap.object.disableEndpointIdentification`.
48+
*/
49+
predicate isPropertyDisableLdapEndpointId(Expr expr) {
50+
expr.(CompileTimeConstantExpr).getStringValue() =
51+
"com.sun.jndi.ldap.object.disableEndpointIdentification"
52+
or
53+
exists(Field f |
54+
expr = f.getAnAccess() and
55+
f.getAnAssignedValue().(StringLiteral).getValue() =
56+
"com.sun.jndi.ldap.object.disableEndpointIdentification"
57+
)
58+
}
59+
60+
/** Holds if an expression is evaluated to the boolean value true. */
61+
predicate isBooleanTrue(Expr expr) {
62+
expr.(CompileTimeConstantExpr).getStringValue() = "true" // "true"
63+
or
64+
expr.(BooleanLiteral).getBooleanValue() = true // true
65+
or
66+
exists(MethodAccess ma |
67+
expr = ma and
68+
ma.getMethod().hasName("toString") and
69+
ma.getQualifier().(FieldAccess).getField().hasName("TRUE") and
70+
ma.getQualifier()
71+
.(FieldAccess)
72+
.getField()
73+
.getDeclaringType()
74+
.hasQualifiedName("java.lang", "Boolean") // Boolean.TRUE.toString()
75+
)
76+
}
77+
78+
/** Holds if `ma` is in a test class or method. */
79+
predicate isTestMethod(MethodAccess ma) {
80+
ma.getEnclosingCallable() instanceof TestMethod or
81+
ma.getEnclosingCallable().getDeclaringType() instanceof TestClass or
82+
ma.getEnclosingCallable().getDeclaringType().getPackage().getName().matches("%test%") or
83+
ma.getEnclosingCallable().getDeclaringType().getName().toLowerCase().matches("%test%")
84+
}
85+
86+
/** Holds if `MethodAccess` ma disables SSL endpoint check. */
87+
predicate isInsecureSSLEndpoint(MethodAccess ma) {
88+
(
89+
ma.getMethod() instanceof SetSystemPropertyMethod and
90+
isPropertyDisableLdapEndpointId(ma.getArgument(0)) and
91+
isBooleanTrue(ma.getArgument(1)) //com.sun.jndi.ldap.object.disableEndpointIdentification=true
92+
or
93+
ma.getMethod() instanceof SetSystemPropertiesMethod and
94+
exists(MethodAccess ma2 |
95+
ma2.getMethod() instanceof SetPropertyMethod and
96+
isPropertyDisableLdapEndpointId(ma2.getArgument(0)) and
97+
isBooleanTrue(ma2.getArgument(1)) and //com.sun.jndi.ldap.object.disableEndpointIdentification=true
98+
ma2.getQualifier().(VarAccess).getVariable().getAnAccess() = ma.getArgument(0) // systemProps.setProperties(properties)
99+
)
100+
)
101+
}
102+
103+
from MethodAccess ma
104+
where
105+
isInsecureSSLEndpoint(ma) and
106+
not isTestMethod(ma)
107+
select ma, "LDAPS configuration allows insecure endpoint identification"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
public class InsecureLdapEndpoint2 {
2+
public Hashtable<String, String> createConnectionEnv() {
3+
Hashtable<String, String> env = new Hashtable<String, String>();
4+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
5+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
6+
7+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
8+
env.put(Context.SECURITY_PRINCIPAL, "username");
9+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
10+
11+
// GOOD - No configuration to disable SSL endpoint check since it is enabled by default.
12+
{
13+
}
14+
15+
return env;
16+
}
17+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| InsecureLdapEndpoint.java:19:9:19:92 | setProperty(...) | LDAPS configuration allows insecure endpoint identification |
2+
| InsecureLdapEndpoint.java:50:9:50:40 | setProperties(...) | LDAPS configuration allows insecure endpoint identification |
3+
| InsecureLdapEndpoint.java:68:9:68:40 | setProperties(...) | LDAPS configuration allows insecure endpoint identification |
4+
| InsecureLdapEndpoint.java:84:9:84:94 | setProperty(...) | LDAPS configuration allows insecure endpoint identification |
5+
| InsecureLdapEndpoint.java:102:9:102:40 | setProperties(...) | LDAPS configuration allows insecure endpoint identification |
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import java.util.Hashtable;
2+
import java.util.Properties;
3+
import javax.naming.Context;
4+
5+
public class InsecureLdapEndpoint {
6+
private static String PROP_DISABLE_LDAP_ENDPOINT_IDENTIFICATION = "com.sun.jndi.ldap.object.disableEndpointIdentification";
7+
8+
// BAD - Test configuration with disabled LDAPS endpoint check using `System.setProperty()`.
9+
public Hashtable<String, String> createConnectionEnv() {
10+
Hashtable<String, String> env = new Hashtable<String, String>();
11+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
12+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
13+
14+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
15+
env.put(Context.SECURITY_PRINCIPAL, "username");
16+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
17+
18+
// Disable SSL endpoint check
19+
System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
20+
21+
return env;
22+
}
23+
24+
// GOOD - Test configuration without disabling LDAPS endpoint check.
25+
public Hashtable<String, String> createConnectionEnv2() {
26+
Hashtable<String, String> env = new Hashtable<String, String>();
27+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
28+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
29+
30+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
31+
env.put(Context.SECURITY_PRINCIPAL, "username");
32+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
33+
34+
return env;
35+
}
36+
37+
// BAD - Test configuration with disabled LDAPS endpoint check using `System.setProperties()`.
38+
public Hashtable<String, String> createConnectionEnv3() {
39+
Hashtable<String, String> env = new Hashtable<String, String>();
40+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
41+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
42+
43+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
44+
env.put(Context.SECURITY_PRINCIPAL, "username");
45+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
46+
47+
// Disable SSL endpoint check
48+
Properties properties = new Properties();
49+
properties.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
50+
System.setProperties(properties);
51+
52+
return env;
53+
}
54+
55+
// BAD - Test configuration with disabled LDAPS endpoint check using `HashTable.put()`.
56+
public Hashtable<String, String> createConnectionEnv4() {
57+
Hashtable<String, String> env = new Hashtable<String, String>();
58+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
59+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
60+
61+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
62+
env.put(Context.SECURITY_PRINCIPAL, "username");
63+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
64+
65+
// Disable SSL endpoint check
66+
Properties properties = new Properties();
67+
properties.put("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
68+
System.setProperties(properties);
69+
70+
return env;
71+
}
72+
73+
// BAD - Test configuration with disabled LDAPS endpoint check using the `TRUE` boolean field.
74+
public Hashtable<String, String> createConnectionEnv5() {
75+
Hashtable<String, String> env = new Hashtable<String, String>();
76+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
77+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
78+
79+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
80+
env.put(Context.SECURITY_PRINCIPAL, "username");
81+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
82+
83+
// Disable SSL endpoint check
84+
System.setProperty(PROP_DISABLE_LDAP_ENDPOINT_IDENTIFICATION, Boolean.TRUE.toString());
85+
86+
return env;
87+
}
88+
89+
// BAD - Test configuration with disabled LDAPS endpoint check using a boolean value.
90+
public Hashtable<String, String> createConnectionEnv6() {
91+
Hashtable<String, String> env = new Hashtable<String, String>();
92+
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
93+
env.put(Context.PROVIDER_URL, "ldaps://ad.your-server.com:636");
94+
95+
env.put(Context.SECURITY_AUTHENTICATION, "simple");
96+
env.put(Context.SECURITY_PRINCIPAL, "username");
97+
env.put(Context.SECURITY_CREDENTIALS, "secpassword");
98+
99+
// Disable SSL endpoint check
100+
Properties properties = new Properties();
101+
properties.put("com.sun.jndi.ldap.object.disableEndpointIdentification", true);
102+
System.setProperties(properties);
103+
104+
return env;
105+
}
106+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql

0 commit comments

Comments
 (0)