-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
egregius313
merged 23 commits into
github:main
from
egregius313:egregius313/promote-insecure-ldap-authentication
Mar 28, 2023
Merged
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 5ff4fcb
Replace `exists` with `any`
egregius313 938d953
Refactor getLeftmostOperand method
egregius313 9275b54
Refactoring the InsecureLdapUrl constructor
egregius313 3936aea
Split Ldap query file into libraries
egregius313 98b445c
Convert test to InlineExpectationsTest
egregius313 05da1dc
Merge concatInsecureLdapString into InsecureLdapUrl constructor
egregius313 6a0167f
Convert to using the new DataFlow modules
egregius313 db60c08
Add security severity
egregius313 0f4709e
Add change note
egregius313 59ce0d7
Documentation changes
egregius313 efdfc2d
Change version of PathNode used to appropriate module
egregius313 752620a
Rename SSL configuration and fix PathGraph
egregius313 cb58936
Documentation changes
egregius313 658c54a
Change names of configuration to fit new naming convention
egregius313 151357d
Make classes/predicates not used outside of query private
egregius313 24d4859
Import changes
egregius313 f28f1af
Add `InsecureLdapUrlSink`
egregius313 0eaf222
Move public classes/predicates to top of library file
egregius313 43d79dc
Apply docs review suggestions
egregius313 106e5e7
Docs review suggestion
egregius313 9bfb13b
Update to the `Global`/`flow*` api
egregius313 97ec808
Make configuration public
egregius313 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
java/ql/lib/semmle/code/java/security/InsecureLdapAuth.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
59
java/ql/lib/semmle/code/java/security/InsecureLdapAuthQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
egregius313 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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>; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
4 changes: 4 additions & 0 deletions
4
java/ql/src/change-notes/2023-03-08-insecure-ldap-authentication.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
24 changes: 0 additions & 24 deletions
24
java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
This file was deleted.
Oops, something went wrong.
27 changes: 0 additions & 27 deletions
27
java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.