Skip to content

Commit 163c888

Browse files
committed
python: port concepts and implementations
1 parent e6b5833 commit 163c888

File tree

3 files changed

+223
-1
lines changed

3 files changed

+223
-1
lines changed

python/ql/lib/semmle/python/Concepts.qll

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,41 @@ module RegexExecution {
443443
}
444444
}
445445

446+
/**
447+
* A data-flow node that executes an LDAP query.
448+
*
449+
* Extend this class to refine existing API models. If you want to model new APIs,
450+
* extend `LDAPQuery::Range` instead.
451+
*/
452+
class LdapExecution extends DataFlow::Node {
453+
LdapExecution::Range range;
454+
455+
LdapExecution() { this = range }
456+
457+
/** Gets the argument containing the filter string. */
458+
DataFlow::Node getFilter() { result = range.getFilter() }
459+
460+
/** Gets the argument containing the base DN. */
461+
DataFlow::Node getBaseDn() { result = range.getBaseDn() }
462+
}
463+
464+
/** Provides classes for modeling new LDAP query execution-related APIs. */
465+
module LdapExecution {
466+
/**
467+
* A data-flow node that executes an LDAP query.
468+
*
469+
* Extend this class to model new APIs. If you want to refine existing API models,
470+
* extend `LDAPQuery` instead.
471+
*/
472+
abstract class Range extends DataFlow::Node {
473+
/** Gets the argument containing the filter string. */
474+
abstract DataFlow::Node getFilter();
475+
476+
/** Gets the argument containing the base DN. */
477+
abstract DataFlow::Node getBaseDn();
478+
}
479+
}
480+
446481
/**
447482
* A data-flow node that escapes meta-characters, which could be used to prevent
448483
* injection attacks.
@@ -500,8 +535,20 @@ module Escaping {
500535
/** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
501536
string getHtmlKind() { result = "html" }
502537

503-
/** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
538+
/** Gets the escape-kind for escaping a string so it can safely be included in a regular expression. */
504539
string getRegexKind() { result = "regex" }
540+
541+
/**
542+
* Gets the escape-kind for escaping a string so it can safely be used as a
543+
* distinguished name (DN) in an LDAP search.
544+
*/
545+
string getLdapDnKind() { result = "ldap_dn" }
546+
547+
/**
548+
* Gets the escape-kind for escaping a string so it can safely be used as a
549+
* filter in an LDAP search.
550+
*/
551+
string getLdapFilterKind() { result = "ldap_filter" }
505552
// TODO: If adding an XML kind, update the modeling of the `MarkupSafe` PyPI package.
506553
//
507554
// Technically it claims to escape for both HTML and XML, but for now we don't have
@@ -526,6 +573,21 @@ class RegexEscaping extends Escaping {
526573
RegexEscaping() { range.getKind() = Escaping::getRegexKind() }
527574
}
528575

576+
/**
577+
* An escape of a string so it can be safely used as a distinguished name (DN)
578+
* in an LDAP search.
579+
*/
580+
class LdapDnEscaping extends Escaping {
581+
LdapDnEscaping() { range.getKind() = Escaping::getLdapDnKind() }
582+
}
583+
584+
/**
585+
* An escape of a string so it can be safely used as a filter in an LDAP search.
586+
*/
587+
class LdapFilterEscaping extends Escaping {
588+
LdapFilterEscaping() { range.getKind() = Escaping::getLdapFilterKind() }
589+
}
590+
529591
/** Provides classes for modeling HTTP-related APIs. */
530592
module HTTP {
531593
import semmle.python.web.HttpConstants
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `python-ldap` PyPI package (imported as `ldap`).
3+
* See https://www.python-ldap.org/en/python-ldap-3.3.0/index.html
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import semmle.python.Concepts
9+
private import semmle.python.ApiGraphs
10+
11+
/**
12+
* Provides models for the `python-ldap` PyPI package (imported as `ldap`).
13+
*
14+
* See https://www.python-ldap.org/en/python-ldap-3.3.0/index.html
15+
*/
16+
private module Ldap {
17+
/**
18+
* The name of an `ldap` method used to execute a query.
19+
*
20+
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#functions
21+
*/
22+
private string ldapQueryMethodName() {
23+
result in ["search", "search_s", "search_st", "search_ext", "search_ext_s"]
24+
}
25+
26+
/** The execution of an `ldap` query. */
27+
private class LdapQueryExecution extends DataFlow::CallCfgNode, LdapExecution::Range {
28+
LdapQueryExecution() {
29+
this =
30+
API::moduleImport("ldap")
31+
.getMember("initialize")
32+
.getReturn()
33+
.getMember(ldapQueryMethodName())
34+
.getACall()
35+
}
36+
37+
override DataFlow::Node getFilter() {
38+
result in [this.getArg(2), this.getArgByName("filterstr")]
39+
}
40+
41+
override DataFlow::Node getBaseDn() { result in [this.getArg(0), this.getArgByName("base")] }
42+
}
43+
44+
/**
45+
* A class to find calls to `ldap.dn.escape_dn_chars`.
46+
*
47+
* See https://github.com/python-ldap/python-ldap/blob/7ce471e238cdd9a4dd8d17baccd1c9e05e6f894a/Lib/ldap/dn.py#L17
48+
*/
49+
private class LdapEscapeDnCall extends DataFlow::CallCfgNode, Escaping::Range {
50+
LdapEscapeDnCall() {
51+
this = API::moduleImport("ldap").getMember("dn").getMember("escape_dn_chars").getACall()
52+
}
53+
54+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("s")] }
55+
56+
override DataFlow::Node getOutput() { result = this }
57+
58+
override string getKind() { result = Escaping::getLdapDnKind() }
59+
}
60+
61+
/**
62+
* A class to find calls to `ldap.filter.escape_filter_chars`.
63+
*
64+
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap-filter.html#ldap.filter.escape_filter_chars
65+
*/
66+
private class LdapEscapeFilterCall extends DataFlow::CallCfgNode, Escaping::Range {
67+
LdapEscapeFilterCall() {
68+
this =
69+
API::moduleImport("ldap").getMember("filter").getMember("escape_filter_chars").getACall()
70+
}
71+
72+
override DataFlow::Node getAnInput() {
73+
result in [this.getArg(0), this.getArgByName("assertion_value")]
74+
}
75+
76+
override DataFlow::Node getOutput() { result = this }
77+
78+
override string getKind() { result = Escaping::getLdapFilterKind() }
79+
}
80+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `ldap3` PyPI package
3+
* See https://pypi.org/project/ldap3/
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import semmle.python.Concepts
9+
private import semmle.python.ApiGraphs
10+
11+
/**
12+
* Provides models for the `ldap3` PyPI package
13+
*
14+
* See https://pypi.org/project/ldap3/
15+
*/
16+
private module Ldap3 {
17+
/** The execution of an `ldap` query. */
18+
private class LdapQueryExecution extends DataFlow::CallCfgNode, LdapExecution::Range {
19+
LdapQueryExecution() {
20+
this =
21+
API::moduleImport("ldap3")
22+
.getMember("Connection")
23+
.getReturn()
24+
.getMember("search")
25+
.getACall()
26+
}
27+
28+
override DataFlow::Node getFilter() {
29+
result in [this.getArg(1), this.getArgByName("search_filter")]
30+
}
31+
32+
override DataFlow::Node getBaseDn() {
33+
result in [this.getArg(0), this.getArgByName("search_base")]
34+
}
35+
}
36+
37+
/**
38+
* A class to find calls to `ldap3.utils.dn.escape_rdn`.
39+
*
40+
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/dn.py#L390
41+
*/
42+
private class LdapEscapeDnCall extends DataFlow::CallCfgNode, Escaping::Range {
43+
LdapEscapeDnCall() {
44+
this =
45+
API::moduleImport("ldap3")
46+
.getMember("utils")
47+
.getMember("dn")
48+
.getMember("escape_rdn")
49+
.getACall()
50+
}
51+
52+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("rdn")] }
53+
54+
override DataFlow::Node getOutput() { result = this }
55+
56+
override string getKind() { result = Escaping::getLdapDnKind() }
57+
}
58+
59+
/**
60+
* A class to find calls to `ldap3.utils.conv.escape_filter_chars`.
61+
*
62+
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/conv.py#L91
63+
*/
64+
private class LdapEscapeFilterCall extends DataFlow::CallCfgNode, Escaping::Range {
65+
LdapEscapeFilterCall() {
66+
this =
67+
API::moduleImport("ldap3")
68+
.getMember("utils")
69+
.getMember("conv")
70+
.getMember("escape_filter_chars")
71+
.getACall()
72+
}
73+
74+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("text")] }
75+
76+
override DataFlow::Node getOutput() { result = this }
77+
78+
override string getKind() { result = Escaping::getLdapFilterKind() }
79+
}
80+
}

0 commit comments

Comments
 (0)