mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Merge pull request #7783 from yoff/python/promote-ldap-injection
Python: promote LDAP injection query
This commit is contained in:
@@ -449,6 +449,44 @@ module RegexExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling LDAP-related APIs. */
|
||||
module LDAP {
|
||||
/**
|
||||
* A data-flow node that executes an LDAP query.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LDAPQuery::Range` instead.
|
||||
*/
|
||||
class LdapExecution extends DataFlow::Node {
|
||||
LdapExecution::Range range;
|
||||
|
||||
LdapExecution() { this = range }
|
||||
|
||||
/** Gets the argument containing the filter string. */
|
||||
DataFlow::Node getFilter() { result = range.getFilter() }
|
||||
|
||||
/** Gets the argument containing the base DN. */
|
||||
DataFlow::Node getBaseDn() { result = range.getBaseDn() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling new LDAP query execution-related APIs. */
|
||||
module LdapExecution {
|
||||
/**
|
||||
* A data-flow node that executes an LDAP query.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LDAPQuery` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument containing the filter string. */
|
||||
abstract DataFlow::Node getFilter();
|
||||
|
||||
/** Gets the argument containing the base DN. */
|
||||
abstract DataFlow::Node getBaseDn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that escapes meta-characters, which could be used to prevent
|
||||
* injection attacks.
|
||||
@@ -506,8 +544,20 @@ module Escaping {
|
||||
/** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
|
||||
string getHtmlKind() { result = "html" }
|
||||
|
||||
/** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
|
||||
/** Gets the escape-kind for escaping a string so it can safely be included in a regular expression. */
|
||||
string getRegexKind() { result = "regex" }
|
||||
|
||||
/**
|
||||
* Gets the escape-kind for escaping a string so it can safely be used as a
|
||||
* distinguished name (DN) in an LDAP search.
|
||||
*/
|
||||
string getLdapDnKind() { result = "ldap_dn" }
|
||||
|
||||
/**
|
||||
* Gets the escape-kind for escaping a string so it can safely be used as a
|
||||
* filter in an LDAP search.
|
||||
*/
|
||||
string getLdapFilterKind() { result = "ldap_filter" }
|
||||
// TODO: If adding an XML kind, update the modeling of the `MarkupSafe` PyPI package.
|
||||
//
|
||||
// Technically it claims to escape for both HTML and XML, but for now we don't have
|
||||
@@ -532,6 +582,21 @@ class RegexEscaping extends Escaping {
|
||||
RegexEscaping() { range.getKind() = Escaping::getRegexKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An escape of a string so it can be safely used as a distinguished name (DN)
|
||||
* in an LDAP search.
|
||||
*/
|
||||
class LdapDnEscaping extends Escaping {
|
||||
LdapDnEscaping() { range.getKind() = Escaping::getLdapDnKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An escape of a string so it can be safely used as a filter in an LDAP search.
|
||||
*/
|
||||
class LdapFilterEscaping extends Escaping {
|
||||
LdapFilterEscaping() { range.getKind() = Escaping::getLdapFilterKind() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP-related APIs. */
|
||||
module HTTP {
|
||||
/** Gets an HTTP verb, in upper case */
|
||||
|
||||
@@ -22,6 +22,8 @@ private import semmle.python.frameworks.FlaskSqlAlchemy
|
||||
private import semmle.python.frameworks.Idna
|
||||
private import semmle.python.frameworks.Invoke
|
||||
private import semmle.python.frameworks.Jmespath
|
||||
private import semmle.python.frameworks.Ldap
|
||||
private import semmle.python.frameworks.Ldap3
|
||||
private import semmle.python.frameworks.MarkupSafe
|
||||
private import semmle.python.frameworks.Multidict
|
||||
private import semmle.python.frameworks.Mysql
|
||||
|
||||
75
python/ql/lib/semmle/python/frameworks/Ldap.qll
Normal file
75
python/ql/lib/semmle/python/frameworks/Ldap.qll
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `python-ldap` PyPI package (imported as `ldap`).
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/index.html
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `python-ldap` PyPI package (imported as `ldap`).
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/index.html
|
||||
*/
|
||||
private module Ldap {
|
||||
/**
|
||||
* The execution of an `ldap` query.
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#functions
|
||||
*/
|
||||
private class LdapQueryExecution extends DataFlow::CallCfgNode, LDAP::LdapExecution::Range {
|
||||
LdapQueryExecution() {
|
||||
this =
|
||||
API::moduleImport("ldap")
|
||||
.getMember("initialize")
|
||||
.getReturn()
|
||||
.getMember(["search", "search_s", "search_st", "search_ext", "search_ext_s"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getFilter() {
|
||||
result in [this.getArg(2), this.getArgByName("filterstr")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getBaseDn() { result in [this.getArg(0), this.getArgByName("base")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ldap.dn.escape_dn_chars`.
|
||||
*
|
||||
* See https://github.com/python-ldap/python-ldap/blob/7ce471e238cdd9a4dd8d17baccd1c9e05e6f894a/Lib/ldap/dn.py#L17
|
||||
*/
|
||||
private class LdapEscapeDnCall extends DataFlow::CallCfgNode, Escaping::Range {
|
||||
LdapEscapeDnCall() {
|
||||
this = API::moduleImport("ldap").getMember("dn").getMember("escape_dn_chars").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("s")] }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getKind() { result = Escaping::getLdapDnKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ldap.filter.escape_filter_chars`.
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap-filter.html#ldap.filter.escape_filter_chars
|
||||
*/
|
||||
private class LdapEscapeFilterCall extends DataFlow::CallCfgNode, Escaping::Range {
|
||||
LdapEscapeFilterCall() {
|
||||
this =
|
||||
API::moduleImport("ldap").getMember("filter").getMember("escape_filter_chars").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result in [this.getArg(0), this.getArgByName("assertion_value")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getKind() { result = Escaping::getLdapFilterKind() }
|
||||
}
|
||||
}
|
||||
80
python/ql/lib/semmle/python/frameworks/Ldap3.qll
Normal file
80
python/ql/lib/semmle/python/frameworks/Ldap3.qll
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `ldap3` PyPI package
|
||||
* See https://pypi.org/project/ldap3/
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `ldap3` PyPI package
|
||||
*
|
||||
* See https://pypi.org/project/ldap3/
|
||||
*/
|
||||
private module Ldap3 {
|
||||
/** The execution of an `ldap` query. */
|
||||
private class LdapQueryExecution extends DataFlow::CallCfgNode, LDAP::LdapExecution::Range {
|
||||
LdapQueryExecution() {
|
||||
this =
|
||||
API::moduleImport("ldap3")
|
||||
.getMember("Connection")
|
||||
.getReturn()
|
||||
.getMember("search")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getFilter() {
|
||||
result in [this.getArg(1), this.getArgByName("search_filter")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getBaseDn() {
|
||||
result in [this.getArg(0), this.getArgByName("search_base")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ldap3.utils.dn.escape_rdn`.
|
||||
*
|
||||
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/dn.py#L390
|
||||
*/
|
||||
private class LdapEscapeDnCall extends DataFlow::CallCfgNode, Escaping::Range {
|
||||
LdapEscapeDnCall() {
|
||||
this =
|
||||
API::moduleImport("ldap3")
|
||||
.getMember("utils")
|
||||
.getMember("dn")
|
||||
.getMember("escape_rdn")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("rdn")] }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getKind() { result = Escaping::getLdapDnKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ldap3.utils.conv.escape_filter_chars`.
|
||||
*
|
||||
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/conv.py#L91
|
||||
*/
|
||||
private class LdapEscapeFilterCall extends DataFlow::CallCfgNode, Escaping::Range {
|
||||
LdapEscapeFilterCall() {
|
||||
this =
|
||||
API::moduleImport("ldap3")
|
||||
.getMember("utils")
|
||||
.getMember("conv")
|
||||
.getMember("escape_filter_chars")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("text")] }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getKind() { result = Escaping::getLdapFilterKind() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Provides taint-tracking configurations for detecting LDAP injection vulnerabilities
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `LdapInjection::Configuration` is needed, otherwise
|
||||
* `LdapInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.Concepts
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
|
||||
/**
|
||||
* Provides aint-tracking configurations for detecting LDAP injection vulnerabilities.class
|
||||
*
|
||||
* Two configurations are provided. One is for detecting LDAP injection
|
||||
* via the distinguished name (DN). The other is for detecting LDAP injection
|
||||
* via the filter. These require different escapings.
|
||||
*/
|
||||
module LdapInjection {
|
||||
import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP injection vulnerabilities
|
||||
* via the distinguished name (DN) parameter of an LDAP search.
|
||||
*/
|
||||
class DnConfiguration extends TaintTracking::Configuration {
|
||||
DnConfiguration() { this = "LdapDnInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof DnSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof DnSanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof DnSanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP injection vulnerabilities
|
||||
* via the filter parameter of an LDAP search.
|
||||
*/
|
||||
class FilterConfiguration extends TaintTracking::Configuration {
|
||||
FilterConfiguration() { this = "LdapFilterInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof FilterSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof FilterSanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof FilterSanitizerGuard
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "ldap injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "ldap injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module LdapInjection {
|
||||
/**
|
||||
* A data flow source for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class DnSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class FilterSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class DnSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class FilterSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class DnSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class FilterSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
|
||||
/**
|
||||
* A logging operation, considered as a flow sink.
|
||||
*/
|
||||
class LdapExecutionAsDnSink extends DnSink {
|
||||
LdapExecutionAsDnSink() { this = any(LDAP::LdapExecution ldap).getBaseDn() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A logging operation, considered as a flow sink.
|
||||
*/
|
||||
class LdapExecutionAsFilterSink extends FilterSink {
|
||||
LdapExecutionAsFilterSink() { this = any(LDAP::LdapExecution ldap).getFilter() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsDnSanitizerGuard extends DnSanitizerGuard, StringConstCompare { }
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsFilterSanitizerGuard extends FilterSanitizerGuard, StringConstCompare {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to replace line breaks functions as a sanitizer.
|
||||
*/
|
||||
class LdapDnEscapingSanitizer extends DnSanitizer, DataFlow::CallCfgNode {
|
||||
LdapDnEscapingSanitizer() { this = any(LdapDnEscaping ldapDnEsc).getOutput() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to replace line breaks functions as a sanitizer.
|
||||
*/
|
||||
class LdapFilterEscapingSanitizer extends FilterSanitizer, DataFlow::CallCfgNode {
|
||||
LdapFilterEscapingSanitizer() { this = any(LdapFilterEscaping ldapDnEsc).getOutput() }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user