Skip to content

Commit c069a5b

Browse files
committed
Factor private host regex into the networking library and enhance the query
1 parent 4ec78d0 commit c069a5b

File tree

6 files changed

+50
-47
lines changed

6 files changed

+50
-47
lines changed

java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,6 @@ import semmle.code.java.frameworks.ApacheHttp
1414
import semmle.code.java.dataflow.TaintTracking
1515
import DataFlow::PathGraph
1616

17-
/**
18-
* Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
19-
*/
20-
private string getPrivateHostRegex() {
21-
result =
22-
"(?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\\]?(?:[:/?#].*)?"
23-
}
24-
2517
/**
2618
* Class of Java URL constructor.
2719
*/
@@ -76,7 +68,7 @@ class HttpStringLiteral extends StringLiteral {
7668
// Match URLs with the HTTP protocol and without private IP addresses to reduce false positives.
7769
exists(string s | this.getRepresentedString() = s |
7870
s.regexpMatch("(?i)http://[\\[a-zA-Z0-9].*") and
79-
not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
71+
not s.substring(7, s.length()) instanceof PrivateHostName
8072
)
8173
}
8274
}
@@ -101,7 +93,7 @@ predicate concatHttpString(Expr protocol, Expr host) {
10193
host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
10294
|
10395
hostString.length() = 0 or // Empty host is loopback address
104-
hostString.regexpMatch(getPrivateHostRegex())
96+
hostString instanceof PrivateHostName
10597
)
10698
}
10799

java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<example>
1313
<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" />
14+
<sample src="InsecureLdapAuth.java" />
1515
</example>
1616

1717
<references>

java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,19 @@
1010

1111
import java
1212
import semmle.code.java.frameworks.Jndi
13+
import semmle.code.java.frameworks.Networking
1314
import semmle.code.java.dataflow.TaintTracking
1415
import DataFlow::PathGraph
1516

1617
/**
17-
* Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
18+
* Insecure (non-SSL, non-private) LDAP URL string literal.
1819
*/
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() {
20+
class InsecureLdapUrlLiteral extends StringLiteral {
21+
InsecureLdapUrlLiteral() {
2922
// Match connection strings with the LDAP protocol and without private IP addresses to reduce false positives.
3023
exists(string s | this.getRepresentedString() = s |
3124
s.regexpMatch("(?i)ldap://[\\[a-zA-Z0-9].*") and
32-
not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
25+
not s.substring(7, s.length()) instanceof PrivateHostName
3326
)
3427
}
3528
}
@@ -47,24 +40,15 @@ class TypeHashtable extends Class {
4740
/**
4841
* Holds if a non-private LDAP string is concatenated from both protocol and host.
4942
*/
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
43+
predicate concatInsecureLdapString(Expr protocol, Expr host) {
44+
protocol.(CompileTimeConstantExpr).getStringValue() = "ldap://" and
6145
not exists(string hostString |
6246
hostString = host.(CompileTimeConstantExpr).getStringValue() or
6347
hostString =
6448
host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
6549
|
6650
hostString.length() = 0 or // Empty host is loopback address
67-
hostString.regexpMatch(getPrivateHostRegex())
51+
hostString instanceof PrivateHostName
6852
)
6953
}
7054

@@ -76,22 +60,21 @@ Expr getLeftmostConcatOperand(Expr expr) {
7660
}
7761

7862
/**
79-
* String concatenated with `LdapStringLiteral`.
63+
* String concatenated with `InsecureLdapUrlLiteral`.
8064
*/
81-
class LdapString extends Expr {
82-
LdapString() {
83-
this instanceof LdapStringLiteral
65+
class InsecureLdapUrl extends Expr {
66+
InsecureLdapUrl() {
67+
this instanceof InsecureLdapUrlLiteral
8468
or
85-
concatLdapString(this.(AddExpr).getLeftOperand(),
69+
concatInsecureLdapString(this.(AddExpr).getLeftOperand(),
8670
getLeftmostConcatOperand(this.(AddExpr).getRightOperand()))
8771
}
8872
}
8973

9074
/**
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)`.
75+
* Holds if `ma` writes the `java.naming.provider.url` (also known as `Context.PROVIDER_URL`) key of a `Hashtable`.
9376
*/
94-
predicate isProviderUrlEnv(MethodAccess ma) {
77+
predicate isProviderUrlSetter(MethodAccess ma) {
9578
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
9679
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
9780
(
@@ -106,8 +89,7 @@ predicate isProviderUrlEnv(MethodAccess ma) {
10689
}
10790

10891
/**
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")`.
92+
* Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
11193
*/
11294
predicate isSimpleAuthEnv(MethodAccess ma) {
11395
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
@@ -132,13 +114,13 @@ class LdapAuthFlowConfig extends TaintTracking::Configuration {
132114
LdapAuthFlowConfig() { this = "InsecureLdapAuth:LdapAuthFlowConfig" }
133115

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

137119
/** Sink of provider URL with simple authentication */
138120
override predicate isSink(DataFlow::Node sink) {
139121
exists(MethodAccess pma |
140122
sink.asExpr() = pma.getArgument(1) and
141-
isProviderUrlEnv(pma) and
123+
isProviderUrlSetter(pma) and
142124
exists(MethodAccess sma |
143125
sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
144126
isSimpleAuthEnv(sma)

java/ql/src/semmle/code/java/frameworks/Networking.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,13 @@ class UrlOpenConnectionMethod extends Method {
129129
this.getName() = "openConnection"
130130
}
131131
}
132+
133+
/**
134+
* A string matching private host names of IPv4 and IPv6, which only matches the host portion therefore checking for port is not necessary.
135+
*/
136+
class PrivateHostName extends string {
137+
bindingset[this]
138+
PrivateHostName() {
139+
this.regexpMatch("(?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\\]?(?:[:/?#].*)?")
140+
}
141+
}

java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ edges
22
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
33
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
44
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl |
5+
| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:47:100:53 | ldapUrl |
56
nodes
67
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
78
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
89
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
910
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
1011
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
1112
| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | semmle.label | ldapUrl |
13+
| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
14+
| InsecureLdapAuth.java:100:47:100:53 | ldapUrl | semmle.label | ldapUrl |
1215
#select
1316
| 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 |
1417
| 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 |
1518
| 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 |
19+
| InsecureLdapAuth.java:100:47:100:53 | ldapUrl | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:47:100:53 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" | LDAP connection string |

java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,19 @@ public void testCleartextLdapAuth3(String ldapUserName, String password) {
8989
environment.put(Context.SECURITY_CREDENTIALS, password);
9090
InitialLdapContext ldapContext = new InitialLdapContext(environment, null);
9191
}
92+
93+
94+
// BAD - Test LDAP authentication in cleartext using `DirContext` and string literals.
95+
public void testCleartextLdapAuth4(String ldapUserName, String password) {
96+
String ldapUrl = "ldap://ad.your-server.com:389";
97+
Hashtable<String, String> environment = new Hashtable<String, String>();
98+
environment.put("java.naming.factory.initial",
99+
"com.sun.jndi.ldap.LdapCtxFactory");
100+
environment.put("java.naming.provider.url", ldapUrl);
101+
environment.put("java.naming.referral", "follow");
102+
environment.put("java.naming.security.authentication", "simple");
103+
environment.put("java.naming.security.principal", ldapUserName);
104+
environment.put("java.naming.security.credentials", password);
105+
DirContext dirContext = new InitialDirContext(environment);
106+
}
92107
}

0 commit comments

Comments
 (0)