-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Python: Add LDAP Injection query #5443
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 35 commits into
github:main
from
jorgectf:jorgectf/python/ldapInjection
May 26, 2021
Merged
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
799d509
Upload LDAP Injection query, qhelp and tests
jorgectf 719b48c
Move to experimental folder
jorgectf 95a1dae
Precision warn and Remove CWE reference
jorgectf 85ec82a
Refactor in progress
jorgectf ad36bea
Refactor LDAP3 stuff (untested)
jorgectf 8223539
Add a test without attributes
jorgectf 3cda2e5
Polish up ldap3 tests
jorgectf 8faafb6
Update Sink
jorgectf 4328ff3
Remove attrs feature
jorgectf 9b43031
Improve Sanitizer calls
jorgectf 1bcb9cd
Simplify query
jorgectf 33423ea
Optimize calls
jorgectf a1850dd
Change LDAP config (qll) filename
jorgectf 8661cb0
Polish LDAP3Query
jorgectf 7296879
Polish tests
jorgectf 3c1ca72
Improve qhelp
jorgectf 1554f4f
Create qhelp examples
jorgectf 95bfdc4
Move tests to /test
jorgectf 4f85de8
Add qlref
jorgectf 7819d1a
Generate .expected
jorgectf b405c67
Add qhelp last newline
jorgectf 82f47f8
Polish metadata
jorgectf cd75433
Fix qhelp examples extension
jorgectf a2e8d88
Write documentation
jorgectf b020ea6
Polish documentation
jorgectf 1c34230
Fix documentation typo
jorgectf c2b96b3
Add documentation to main classes' functions.
jorgectf 34b8af3
Move structure to LDAP.qll
jorgectf 6159fbe
Update functions naming
jorgectf 2ad72ad
Add LDAP framework entry in Frameworks.qll
jorgectf 8665747
Update sink and sanitizer to match new naming
jorgectf 9e9678b
Apply documentation suggestions
jorgectf 37d6ff7
Update tests and .expected
jorgectf d5f2846
Merge branch 'main' into jorgectf/python/ldapInjection
RasmusWL f807c2f
Python: autoformat
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
28 changes: 28 additions & 0 deletions
28
python/ql/src/experimental/Security/CWE-090/LDAPInjection.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,28 @@ | ||
| <!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd"> | ||
|
|
||
| <qhelp> | ||
| <overview> | ||
| <p>If an LDAP query is built by a not sanitized user-provided value, a user is likely to be able to run malicious LDAP queries.</p> | ||
| </overview> | ||
|
|
||
| <recommendation> | ||
| <p>In case user input must compose an LDAP query, it should be escaped in order to avoid a malicious user supplying special characters that change the actual purpose of the query. To do so, functions that ldap frameworks provide such as <code>escape_filter_chars</code> should be applied to that user input. | ||
| <recommendation> | ||
|
|
||
|
|
||
| <references> | ||
| <li> | ||
| OWASP | ||
| <a href="https://owasp.org/www-community/attacks/LDAP_Injection">LDAP Injection</a> | ||
| </li> | ||
| <li> | ||
| SonarSource | ||
| <a href="https://rules.sonarsource.com/python/RSPEC-2078">RSPEC-2078</a> | ||
| </li> | ||
| <li> | ||
| Python | ||
| <a href="https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html">LDAP Documentation</a> | ||
| </li> | ||
| </references> | ||
|
|
||
| </qhelp> |
24 changes: 24 additions & 0 deletions
24
python/ql/src/experimental/Security/CWE-090/LDAPInjection.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,24 @@ | ||
| /** | ||
| * @name Python LDAP Injection | ||
| * @description Python LDAP Injection through search filter | ||
| * @kind path-problem | ||
| * @problem.severity error | ||
| * @id python/ldap-injection | ||
| * @tags experimental | ||
| * security | ||
| * external/cwe/cwe-090 | ||
| */ | ||
|
|
||
| // Determine precision above | ||
| import python | ||
| import experimental.semmle.python.security.injection.LDAPInjection | ||
| import DataFlow::PathGraph | ||
|
|
||
| from | ||
| LDAPInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink, | ||
| LDAPInjectionSink castedSink | ||
| where | ||
| config.hasFlowPath(source, sink) and | ||
| castedSink.getLDAPNode() = sink.getNode() | ||
| select sink.getNode(), source, sink, "$@ LDAP query executes $@ as a $@.", castedSink, "This", | ||
| source.getNode(), "a user-provided value", castedSink.getLDAPNode(), castedSink.getLDAPPart() |
38 changes: 38 additions & 0 deletions
38
python/ql/src/experimental/Security/CWE-090/unit_tests/ldap3_bad.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,38 @@ | ||
| from flask import request, Flask | ||
| import ldap3 | ||
|
|
||
| app = Flask(__name__) | ||
|
|
||
|
|
||
| @app.route("/normal") | ||
| def normal(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| srv = ldap3.Server('ldap://127.0.0.1', port=1337) | ||
| conn = ldap3.Connection(srv, user=unsafe_dn, auto_bind=True) | ||
| conn.search(unsafe_dn, unsafe_filter, attributes=[ | ||
| "testAttr1", "testAttr2"]) | ||
|
|
||
|
|
||
| @app.route("/normal_noAttrs") | ||
| def normal_noAttrs(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| srv = ldap3.Server('ldap://127.0.0.1', port=1337) | ||
| conn = ldap3.Connection(srv, user=unsafe_dn, auto_bind=True) | ||
| conn.search(unsafe_dn, unsafe_filter) | ||
|
|
||
|
|
||
| @app.route("/direct") | ||
| def direct(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| srv = ldap3.Server('ldap://127.0.0.1', port=1337) | ||
| conn = ldap3.Connection(srv, user=unsafe_dn, auto_bind=True).search(unsafe_dn, unsafe_filter, attributes=[ | ||
| "testAttr1", "testAttr2"]) | ||
|
|
||
| # if __name__ == "__main__": | ||
| # app.run(debug=True) |
49 changes: 49 additions & 0 deletions
49
python/ql/src/experimental/Security/CWE-090/unit_tests/ldap3_good.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,49 @@ | ||
| from flask import request, Flask | ||
| import ldap3 | ||
| from ldap3.utils.dn import escape_rdn | ||
| from ldap3.utils.conv import escape_filter_chars | ||
|
|
||
| app = Flask(__name__) | ||
|
|
||
|
|
||
| @app.route("/normal") | ||
| def normal(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = escape_rdn(unsafe_dn) | ||
| safe_filter = escape_filter_chars(unsafe_filter) | ||
|
|
||
| srv = ldap3.Server('ldap://127.0.0.1', port=1337) | ||
| conn = ldap3.Connection(srv, user=unsafe_dn, auto_bind=True) | ||
| conn.search(safe_dn, safe_filter, attributes=[ | ||
| "testAttr1", "testAttr2"]) | ||
|
|
||
|
|
||
| @app.route("/normal_noAttrs") | ||
| def normal_noAttrs(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = escape_rdn(unsafe_dn) | ||
| safe_filter = escape_filter_chars(unsafe_filter) | ||
|
|
||
| srv = ldap3.Server('ldap://127.0.0.1', port=1337) | ||
| conn = ldap3.Connection(srv, user=unsafe_dn, auto_bind=True) | ||
| conn.search(safe_dn, safe_filter) | ||
|
|
||
|
|
||
| @app.route("/direct") | ||
| def direct(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = escape_rdn(unsafe_dn) | ||
| safe_filter = escape_filter_chars(unsafe_filter) | ||
|
|
||
| srv = ldap3.Server('ldap://127.0.0.1', port=1337) | ||
| conn = ldap3.Connection(srv, user=unsafe_dn, auto_bind=True).search(safe_dn, safe_filter, attributes=[ | ||
| "testAttr1", "testAttr2"]) | ||
|
|
||
| # if __name__ == "__main__": | ||
| # app.run(debug=True) |
56 changes: 56 additions & 0 deletions
56
python/ql/src/experimental/Security/CWE-090/unit_tests/ldap_bad.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,56 @@ | ||
| from flask import request, Flask | ||
| import ldap | ||
|
|
||
| app = Flask(__name__) | ||
|
|
||
|
|
||
| @app.route("/normal") | ||
| def normal(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| ldap_connection = ldap.initialize("ldap://127.0.0.1:1337") | ||
| user = ldap_connection.search_s( | ||
| unsafe_dn, ldap.SCOPE_SUBTREE, unsafe_filter, ["testAttr1", "testAttr2"]) | ||
|
|
||
|
|
||
| @app.route("/normal_noAttrs") | ||
| def normal_noAttrs(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| ldap_connection = ldap.initialize("ldap://127.0.0.1:1337") | ||
| user = ldap_connection.search_s( | ||
| unsafe_dn, ldap.SCOPE_SUBTREE, unsafe_filter) | ||
|
|
||
|
|
||
| @app.route("/direct") | ||
| def direct(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| user = ldap.initialize("ldap://127.0.0.1:1337").search_s( | ||
| unsafe_dn, ldap.SCOPE_SUBTREE, unsafe_filter, ["testAttr1", "testAttr2"]) | ||
|
|
||
|
|
||
| @app.route("/normal_argbyname") | ||
| def normal_argbyname(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| ldap_connection = ldap.initialize("ldap://127.0.0.1:1337") | ||
| user = ldap_connection.search_s( | ||
| unsafe_dn, ldap.SCOPE_SUBTREE, attrlist=["testAttr1", "testAttr2"], filterstr=unsafe_filter) | ||
|
|
||
|
|
||
| @app.route("/direct_argbyname") | ||
| def direct_argbyname(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| user = ldap.initialize("ldap://127.0.0.1:1337").search_s( | ||
| unsafe_dn, ldap.SCOPE_SUBTREE, attrlist=["testAttr1", "testAttr2"], filterstr=unsafe_filter) | ||
|
|
||
|
|
||
| # if __name__ == "__main__": | ||
| # app.run(debug=True) |
73 changes: 73 additions & 0 deletions
73
python/ql/src/experimental/Security/CWE-090/unit_tests/ldap_good.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,73 @@ | ||
| from flask import request, Flask | ||
| import ldap | ||
| import ldap.filter | ||
| import ldap.dn | ||
|
|
||
| app = Flask(__name__) | ||
|
|
||
|
|
||
| @app.route("/normal") | ||
| def normal(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = ldap.dn.escape_dn_chars(unsafe_dn) | ||
| safe_filter = ldap.filter.escape_filter_chars(unsafe_filter) | ||
|
|
||
| ldap_connection = ldap.initialize("ldap://127.0.0.1:1337") | ||
| user = ldap_connection.search_s( | ||
| safe_dn, ldap.SCOPE_SUBTREE, safe_filter, ["testAttr1", "testAttr2"]) | ||
|
|
||
|
|
||
| @app.route("/normal_noAttrs") | ||
| def normal_noAttrs(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = ldap.dn.escape_dn_chars(unsafe_dn) | ||
| safe_filter = ldap.filter.escape_filter_chars(unsafe_filter) | ||
|
|
||
| ldap_connection = ldap.initialize("ldap://127.0.0.1:1337") | ||
| user = ldap_connection.search_s( | ||
| safe_dn, ldap.SCOPE_SUBTREE, safe_filter) | ||
|
|
||
|
|
||
| @app.route("/direct") | ||
| def direct(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = ldap.dn.escape_dn_chars(unsafe_dn) | ||
| safe_filter = ldap.filter.escape_filter_chars(unsafe_filter) | ||
|
|
||
| user = ldap.initialize("ldap://127.0.0.1:1337").search_s( | ||
| safe_dn, ldap.SCOPE_SUBTREE, safe_filter, ["testAttr1", "testAttr2"]) | ||
|
|
||
|
|
||
| @app.route("/normal_argbyname") | ||
| def normal_argbyname(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = ldap.dn.escape_dn_chars(unsafe_dn) | ||
| safe_filter = ldap.filter.escape_filter_chars(unsafe_filter) | ||
|
|
||
| ldap_connection = ldap.initialize("ldap://127.0.0.1:1337") | ||
| user = ldap_connection.search_s( | ||
| safe_dn, ldap.SCOPE_SUBTREE, attrlist=["testAttr1", "testAttr2"], filterstr=safe_filter) | ||
|
|
||
|
|
||
| @app.route("/direct_argbyname") | ||
| def direct_argbyname(): | ||
| unsafe_dn = "dc=%s" % request.args['dc'] | ||
| unsafe_filter = "(user=%s)" % request.args['username'] | ||
|
|
||
| safe_dn = ldap.dn.escape_dn_chars(unsafe_dn) | ||
| safe_filter = ldap.filter.escape_filter_chars(unsafe_filter) | ||
|
|
||
| user = ldap.initialize("ldap://127.0.0.1:1337").search_s( | ||
| safe_dn, ldap.SCOPE_SUBTREE, attrlist=["testAttr1", "testAttr2"], filterstr=safe_filter) | ||
|
|
||
|
|
||
| # if __name__ == "__main__": | ||
| # app.run(debug=True) |
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
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.