Merge pull request #7783 from yoff/python/promote-ldap-injection

Python: promote LDAP injection query
This commit is contained in:
Rasmus Wriedt Larsen
2022-02-15 10:24:18 +01:00
committed by GitHub
23 changed files with 441 additions and 67 deletions

View File

@@ -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 */

View File

@@ -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

View 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() }
}
}

View 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() }
}
}

View File

@@ -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
}
}
}

View File

@@ -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() }
}
}