-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Python: Add LDAP Insecure Authentication query #5445
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
RasmusWL
merged 23 commits into
github:main
from
jorgectf:jorgectf/python/ldapinsecureauth
Sep 23, 2021
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
7de9214
Upload LDAP Insecure authentication query and tests
jorgectf 3ce0a9c
Move to experimental folder
jorgectf 957b3e1
Precision warn
jorgectf edb273a
Merge remote-tracking branch 'origin/jorgectf/python/ldapimproperauth…
jorgectf a34d6d3
Port to ApiGraphs and finish the query
jorgectf b03e75e
Extend `ldap3`'s `start_tls` and fix tests
jorgectf f02b6d6
Merge branch 'github:main' into jorgectf/python/ldapinsecureauth
jorgectf d458464
Apply suggestions from code review
jorgectf 786edb7
Update `.expected`
jorgectf 64b305c
Add `.qhelp` along with its example
jorgectf 1bc16fb
Apply suggestions from code review
jorgectf ee98c0c
Add `start_tls_s()` comment and use `DataFlow::MethodCallNode` instead
jorgectf b802d79
Fix `OPT_X_TLS_` mandatory options
jorgectf 8008011
Fix taint tracking comment
jorgectf 4e261c6
Optimize `concatAndCompareAgainstFullHostRegex`
jorgectf 54012eb
Optimize `getFullHostRegex`
jorgectf 18b05bc
Fix tests and add global option
jorgectf 3cf28ad
Merge remote-tracking branch 'origin/main' into jorgectf/python/ldapi…
jorgectf 353c0a9
Add missing comment
jorgectf 2ccc6dc
Merge branch 'main' into jorgectf/python/ldapinsecureauth
jorgectf b505662
Fix global test and update `.expected`
jorgectf 70489b2
Merge branch 'main' into jorgectf/python/ldapinsecureauth
RasmusWL ef6e502
Python: Make LDAP global options test better
RasmusWL 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
23 changes: 23 additions & 0 deletions
23
python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.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,23 @@ | ||
<!DOCTYPE qhelp PUBLIC | ||
"-//Semmle//qhelp//EN" | ||
"qhelp.dtd"> | ||
<qhelp> | ||
|
||
<overview> | ||
<p>Failing to ensure the utilization of SSL in an LDAP connection can cause the entire communication | ||
to be sent in cleartext making it easier for an attacker to intercept it.</p> | ||
</overview> | ||
|
||
<recommendation> | ||
<p>Always set <code>use_SSL</code> to <code>True</code>, call <code>start_tls_s()</code> or set a proper option flag (<code>ldap.OPT_X_TLS_XXXXXX</code>).</p> | ||
</recommendation> | ||
|
||
<example> | ||
<p>This example shows both good and bad ways to deal with this issue under Python 3.</p> | ||
|
||
<p>The first one sets <code>use_SSL</code> to true as a keyword argument whereas the second one fails to provide a value for it, so | ||
the default one is used (<code>False</code>).</p> | ||
<sample src="examples/LDAPInsecureAuth.py" /> | ||
</example> | ||
|
||
</qhelp> |
21 changes: 21 additions & 0 deletions
21
python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
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,21 @@ | ||
/** | ||
* @name Python Insecure LDAP Authentication | ||
* @description Python LDAP Insecure LDAP Authentication | ||
* @kind path-problem | ||
* @problem.severity error | ||
* @id py/insecure-ldap-auth | ||
* @tags experimental | ||
* security | ||
* external/cwe/cwe-522 | ||
* external/cwe/cwe-523 | ||
*/ | ||
|
||
// determine precision above | ||
import python | ||
import DataFlow::PathGraph | ||
import experimental.semmle.python.security.LDAPInsecureAuth | ||
|
||
from LDAPInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink | ||
where config.hasFlowPath(source, sink) | ||
select sink.getNode(), source, sink, "$@ is authenticated insecurely.", sink.getNode(), | ||
"This LDAP host" |
20 changes: 20 additions & 0 deletions
20
python/ql/src/experimental/Security/CWE-522/examples/LDAPInsecureAuth.py
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,20 @@ | ||
from ldap3 import Server, Connection, ALL | ||
from flask import request, Flask | ||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route("/good") | ||
def good(): | ||
srv = Server(host, port, use_ssl=True) | ||
conn = Connection(srv, dn, password) | ||
conn.search(dn, search_filter) | ||
return conn.response | ||
|
||
|
||
@app.route("/bad") | ||
def bad(): | ||
srv = Server(host, port) | ||
conn = Connection(srv, dn, password) | ||
conn.search(dn, search_filter) | ||
return conn.response |
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
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
106 changes: 106 additions & 0 deletions
106
python/ql/src/experimental/semmle/python/security/LDAPInsecureAuth.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,106 @@ | ||
/** | ||
* Provides a taint-tracking configuration for detecting LDAP injection vulnerabilities | ||
*/ | ||
|
||
import python | ||
import semmle.python.dataflow.new.DataFlow | ||
import semmle.python.dataflow.new.TaintTracking | ||
import semmle.python.dataflow.new.RemoteFlowSources | ||
import experimental.semmle.python.Concepts | ||
|
||
string getFullHostRegex() { result = "(?i)ldap://.+" } | ||
|
||
string getSchemaRegex() { result = "(?i)ldap(://)?" } | ||
|
||
string getPrivateHostRegex() { | ||
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\\]?(?:[:/?#].*)?" | ||
} | ||
|
||
// "ldap://somethingon.theinternet.com" | ||
class LDAPFullHost extends StrConst { | ||
LDAPFullHost() { | ||
exists(string s | | ||
s = this.getText() and | ||
s.regexpMatch(getFullHostRegex()) and | ||
// check what comes after the `ldap://` prefix | ||
not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex()) | ||
) | ||
} | ||
} | ||
|
||
class LDAPSchema extends StrConst { | ||
LDAPSchema() { this.getText().regexpMatch(getSchemaRegex()) } | ||
} | ||
|
||
class LDAPPrivateHost extends StrConst { | ||
LDAPPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) } | ||
} | ||
|
||
predicate concatAndCompareAgainstFullHostRegex(LDAPSchema schema, StrConst host) { | ||
not host instanceof LDAPPrivateHost and | ||
(schema.getText() + host.getText()).regexpMatch(getFullHostRegex()) | ||
} | ||
|
||
// "ldap://" + "somethingon.theinternet.com" | ||
class LDAPBothStrings extends BinaryExpr { | ||
LDAPBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) } | ||
} | ||
|
||
// schema + host | ||
class LDAPBothVar extends BinaryExpr { | ||
LDAPBothVar() { | ||
exists(SsaVariable schemaVar, SsaVariable hostVar | | ||
this.getLeft() = schemaVar.getVariable().getALoad() and // getAUse is incompatible with Expr | ||
this.getRight() = hostVar.getVariable().getALoad() and | ||
concatAndCompareAgainstFullHostRegex(schemaVar | ||
.getDefinition() | ||
.getImmediateDominator() | ||
.getNode(), hostVar.getDefinition().getImmediateDominator().getNode()) | ||
) | ||
} | ||
} | ||
jorgectf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// schema + "somethingon.theinternet.com" | ||
class LDAPVarString extends BinaryExpr { | ||
LDAPVarString() { | ||
exists(SsaVariable schemaVar | | ||
this.getLeft() = schemaVar.getVariable().getALoad() and | ||
concatAndCompareAgainstFullHostRegex(schemaVar | ||
.getDefinition() | ||
.getImmediateDominator() | ||
.getNode(), this.getRight()) | ||
) | ||
} | ||
} | ||
|
||
// "ldap://" + host | ||
class LDAPStringVar extends BinaryExpr { | ||
LDAPStringVar() { | ||
exists(SsaVariable hostVar | | ||
this.getRight() = hostVar.getVariable().getALoad() and | ||
concatAndCompareAgainstFullHostRegex(this.getLeft(), | ||
hostVar.getDefinition().getImmediateDominator().getNode()) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A taint-tracking configuration for detecting LDAP insecure authentications. | ||
*/ | ||
class LDAPInsecureAuthConfig extends TaintTracking::Configuration { | ||
LDAPInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" } | ||
|
||
override predicate isSource(DataFlow::Node source) { | ||
source instanceof RemoteFlowSource or | ||
source.asExpr() instanceof LDAPFullHost or | ||
source.asExpr() instanceof LDAPBothStrings or | ||
source.asExpr() instanceof LDAPBothVar or | ||
source.asExpr() instanceof LDAPVarString or | ||
source.asExpr() instanceof LDAPStringVar | ||
} | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
exists(LDAPBind ldapBind | not ldapBind.useSSL() and sink = ldapBind.getHost()) | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
...ql/test/experimental/query-tests/Security/CWE-522-global-option/LDAPInsecureAuth.expected
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 @@ | ||
edges | ||
nodes | ||
subpaths | ||
#select |
1 change: 1 addition & 0 deletions
1
...on/ql/test/experimental/query-tests/Security/CWE-522-global-option/LDAPInsecureAuth.qlref
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 @@ | ||
experimental/Security/CWE-522/LDAPInsecureAuth.ql |
29 changes: 29 additions & 0 deletions
29
python/ql/test/experimental/query-tests/Security/CWE-522-global-option/ldap2_global.py
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,29 @@ | ||
# since global options are considered to affect all files in a repo, we need to keep | ||
# this test in its' own directory (so it doesn't interfere with other tests). | ||
|
||
import ldap | ||
from flask import request, Flask | ||
|
||
app = Flask(__name__) | ||
|
||
# GOOD | ||
|
||
# SSL through ldap global variable option | ||
|
||
ldap.set_option(ldap.OPT_X_TLS_DEMAND, True) | ||
|
||
|
||
@app.route("/one") | ||
def one(): | ||
# The following connection would have been insecure if the global option above was | ||
# not set | ||
ldap_connection_5 = ldap.initialize("ldap://somethingon.theinternet.com") | ||
ldap_connection_5.simple_bind_s('', '') | ||
user = ldap_connection_5.search_s( | ||
"dn", ldap.SCOPE_SUBTREE, "search_filter") | ||
|
||
return user | ||
|
||
|
||
# if __name__ == "__main__": | ||
# app.run(debug=True) |
28 changes: 28 additions & 0 deletions
28
python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
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,28 @@ | ||
edges | ||
| ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | | ||
| ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | | ||
| ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | | ||
| ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute | | ||
| ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute | ldap3_remote.py:138:21:138:40 | ControlFlowNode for Subscript | | ||
| ldap3_remote.py:138:21:138:40 | ControlFlowNode for Subscript | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | | ||
nodes | ||
| ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host | | ||
| ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host | | ||
| ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host | | ||
| ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | ||
| ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | ||
| ldap3_remote.py:138:21:138:40 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | | ||
| ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host | | ||
subpaths | ||
#select | ||
| ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | $@ is authenticated insecurely. | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | This LDAP host | | ||
| ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | $@ is authenticated insecurely. | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | This LDAP host | | ||
| ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | This LDAP host | | ||
| ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | This LDAP host | | ||
| ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | This LDAP host | | ||
| ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | This LDAP host | |
1 change: 1 addition & 0 deletions
1
python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.qlref
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 @@ | ||
experimental/Security/CWE-522/LDAPInsecureAuth.ql |
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.