104 lines
No EOL
3 KiB
Text
104 lines
No EOL
3 KiB
Text
// a query
|
|
|
|
/**
|
|
* @name LDAP query built from user-controlled sources
|
|
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
|
|
* malicious LDAP code by the user.
|
|
* @kind path-problem
|
|
* @problem.severity error
|
|
* @id py/ldap-injection
|
|
* @tags experimental
|
|
* security
|
|
* external/cwe/cwe-090
|
|
*/
|
|
|
|
import python
|
|
import experimental.semmle.python.security.injection.LDAP
|
|
import DataFlow::PathGraph
|
|
|
|
from LDAPInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
|
where config.hasFlowPath(source, sink)
|
|
select sink.getNode(), source, sink, "$@ LDAP query parameter comes from $@.", sink.getNode(),
|
|
"This", source.getNode(), "a user-provided value"
|
|
|
|
// a concept
|
|
|
|
module LDAPEscape {
|
|
abstract class Range extends DataFlow::Node {
|
|
abstract DataFlow::Node getAnInput();
|
|
}
|
|
}
|
|
|
|
class LDAPEscape extends DataFlow::Node {
|
|
LDAPEscape::Range range;
|
|
|
|
LDAPEscape() { this = range }
|
|
|
|
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
|
}
|
|
|
|
// a library modeling
|
|
|
|
private module LDAP2 {
|
|
private class LDAP2QueryMethods extends string {
|
|
LDAP2QueryMethods() {
|
|
this in ["search", "search_s", "search_st", "search_ext", "search_ext_s"]
|
|
}
|
|
}
|
|
|
|
private class LDAP2Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
|
|
DataFlow::Node ldapQuery;
|
|
|
|
LDAP2Query() {
|
|
exists(DataFlow::AttrRead searchMethod |
|
|
this.getFunction() = searchMethod and
|
|
API::moduleImport("ldap").getMember("initialize").getACall() =
|
|
searchMethod.getObject().getALocalSource() and
|
|
searchMethod.getAttributeName() instanceof LDAP2QueryMethods and
|
|
(
|
|
ldapQuery = this.getArg(0)
|
|
or
|
|
(
|
|
ldapQuery = this.getArg(2) or
|
|
ldapQuery = this.getArgByName("filterstr")
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
override DataFlow::Node getQuery() { result = ldapQuery }
|
|
}
|
|
|
|
private class LDAP2EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
|
LDAP2EscapeDNCall() {
|
|
this = API::moduleImport("ldap").getMember("dn").getMember("escape_dn_chars").getACall()
|
|
}
|
|
|
|
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
|
}
|
|
|
|
private class LDAP2EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
|
LDAP2EscapeFilterCall() {
|
|
this =
|
|
API::moduleImport("ldap").getMember("filter").getMember("escape_filter_chars").getACall()
|
|
}
|
|
|
|
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
|
}
|
|
}
|
|
|
|
// a taint flow config
|
|
|
|
class LDAPInjectionFlowConfig extends TaintTracking::Configuration {
|
|
LDAPInjectionFlowConfig() { this = "LDAPInjectionFlowConfig" }
|
|
|
|
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
|
|
|
override predicate isSink(DataFlow::Node sink) { sink = any(LDAPQuery ldapQuery).getQuery() }
|
|
|
|
override predicate isSanitizer(DataFlow::Node sanitizer) {
|
|
sanitizer = any(LDAPEscape ldapEsc).getAnInput()
|
|
}
|
|
}
|
|
|
|
// From https://github.com/github/codeql/pull/5443/files |