Skip to content

Commit 026d94c

Browse files
committed
Add LDAP Injection query (incomplete)
1 parent 202037e commit 026d94c

File tree

6 files changed

+261
-8
lines changed

6 files changed

+261
-8
lines changed

ruby/ql/lib/codeql/ruby/Concepts.qll

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,7 @@ module FileSystemWriteAccess {
212212
* Extend this class to refine existing API models. If you want to model new APIs,
213213
* extend `FileSystemPermissionModification::Range` instead.
214214
*/
215-
class FileSystemPermissionModification extends DataFlow::Node instanceof FileSystemPermissionModification::Range
216-
{
215+
class FileSystemPermissionModification extends DataFlow::Node instanceof FileSystemPermissionModification::Range {
217216
/**
218217
* Gets an argument to this permission modification that is interpreted as a
219218
* set of permissions.
@@ -469,8 +468,7 @@ module Http {
469468
}
470469
}
471470

472-
private class RequestInputAccessAsRemoteFlowSource extends RemoteFlowSource::Range instanceof RequestInputAccess
473-
{
471+
private class RequestInputAccessAsRemoteFlowSource extends RemoteFlowSource::Range instanceof RequestInputAccess {
474472
override string getSourceType() { result = this.(RequestInputAccess).getSourceType() }
475473
}
476474

@@ -959,8 +957,7 @@ module Path {
959957
* Extend this class to refine existing API models. If you want to model new APIs,
960958
* extend `CookieSecurityConfigurationSetting::Range` instead.
961959
*/
962-
class CookieSecurityConfigurationSetting extends DataFlow::Node instanceof CookieSecurityConfigurationSetting::Range
963-
{
960+
class CookieSecurityConfigurationSetting extends DataFlow::Node instanceof CookieSecurityConfigurationSetting::Range {
964961
/**
965962
* Gets a description of how this cookie setting may weaken application security.
966963
* This predicate has no results if the setting is considered to be safe.
@@ -1040,8 +1037,7 @@ module Cryptography {
10401037
* Extend this class to refine existing API models. If you want to model new APIs,
10411038
* extend `CryptographicOperation::Range` instead.
10421039
*/
1043-
class CryptographicOperation extends SC::CryptographicOperation instanceof CryptographicOperation::Range
1044-
{
1040+
class CryptographicOperation extends SC::CryptographicOperation instanceof CryptographicOperation::Range {
10451041
/** DEPRECATED: Use `getAlgorithm().isWeak() or getBlockMode().isWeak()` instead */
10461042
deprecated predicate isWeak() { super.isWeak() }
10471043
}
@@ -1129,3 +1125,69 @@ module TemplateRendering {
11291125
abstract DataFlow::Node getTemplate();
11301126
}
11311127
}
1128+
1129+
/**
1130+
* A data-flow node that constructs a LDAP query.
1131+
*
1132+
* Often, it is worthy of an alert if an LDAP query is constructed such that
1133+
* executing it would be a security risk.
1134+
*
1135+
* If it is important that the query is executed, use `LdapExecution`.
1136+
*
1137+
* Extend this class to refine existing API models. If you want to model new APIs,
1138+
* extend `LdapConstruction::Range` instead.
1139+
*/
1140+
class LdapConstruction extends DataFlow::Node instanceof LdapConstruction::Range {
1141+
/** Gets the argument that specifies the query to be constructed. */
1142+
DataFlow::Node getQuery() { result = super.getQuery() }
1143+
}
1144+
1145+
/** Provides a class for modeling new LDAP query construction APIs. */
1146+
module LdapConstruction {
1147+
/**
1148+
* A data-flow node that constructs a LDAP query.
1149+
*
1150+
* Often, it is worthy of an alert if an LDAP query is constructed such that
1151+
* executing it would be a security risk.
1152+
*
1153+
* If it is important that the query is executed, use `LdapExecution`.
1154+
*
1155+
* Extend this class to model new APIs. If you want to refine existing API models,
1156+
* extend `LdapConstruction` instead.
1157+
*/
1158+
abstract class Range extends DataFlow::Node {
1159+
/** Gets the argument that specifies the query to be constructed. */
1160+
abstract DataFlow::Node getQuery();
1161+
}
1162+
}
1163+
1164+
/**
1165+
* A data-flow node that executes LDAP queries.
1166+
*
1167+
* If the context of interest is such that merely constructing a LDAP query
1168+
* would be valuable to report, consider using `LdapConstruction`.
1169+
*
1170+
* Extend this class to refine existing API models. If you want to model new APIs,
1171+
* extend `LdapExecution::Range` instead.
1172+
*/
1173+
class LdapExecution extends DataFlow::Node instanceof LdapExecution::Range {
1174+
/** Gets the argument that specifies the query to be executed. */
1175+
DataFlow::Node getQuery() { result = super.getQuery() }
1176+
}
1177+
1178+
/** Provides a class for modeling new LDAP query execution APIs. */
1179+
module LdapExecution {
1180+
/**
1181+
* A data-flow node that executes LDAP queries.
1182+
*
1183+
* If the context of interest is such that merely constructing a LDAP query
1184+
* would be valuable to report, consider using `LdapConstruction`.
1185+
*
1186+
* Extend this class to model new APIs. If you want to refine existing API models,
1187+
* extend `LdapExecution` instead.
1188+
*/
1189+
abstract class Range extends DataFlow::Node {
1190+
//** Gets the argument that specifies the query to be executed. */
1191+
abstract DataFlow::Node getQuery();
1192+
}
1193+
}

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ private import codeql.ruby.frameworks.Slim
3232
private import codeql.ruby.frameworks.Sinatra
3333
private import codeql.ruby.frameworks.Twirp
3434
private import codeql.ruby.frameworks.Sqlite3
35+
private import codeql.ruby.frameworks.Ldap
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Provides modeling for `net-ldap` a ruby library for LDAP.
3+
*/
4+
5+
private import ruby
6+
private import codeql.ruby.ApiGraphs
7+
private import codeql.ruby.dataflow.FlowSummary
8+
private import codeql.ruby.Concepts
9+
private import codeql.ruby.CFG
10+
private import codeql.ruby.AST
11+
private import codeql.ruby.Concepts
12+
private import codeql.ruby.controlflow.CfgNodes
13+
private import codeql.ruby.DataFlow
14+
private import codeql.ruby.dataflow.internal.DataFlowDispatch
15+
private import codeql.ruby.dataflow.internal.DataFlowPrivate
16+
private import codeql.ruby.ApiGraphs
17+
private import codeql.ruby.frameworks.Stdlib
18+
private import codeql.ruby.frameworks.Core
19+
20+
/**
21+
* Provides modeling for `net-ldap` a ruby library for LDAP.
22+
*/
23+
module NetLdap {
24+
/**
25+
* Flow summary for `Net::LDAP.new`. This method establishes a connection to a LDAP server.
26+
*/
27+
private class LdapSummary extends SummarizedCallable {
28+
LdapSummary() { this = "Net::LDAP.new" }
29+
30+
override MethodCall getACall() { result = any(LdapConnection l).asExpr().getExpr() }
31+
32+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
33+
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
34+
}
35+
}
36+
37+
private class LdapConnection extends DataFlow::CallNode {
38+
LdapConnection() {
39+
this = API::getTopLevelMember("Net").getMember("LDAP").getAnInstantiation() or
40+
this = API::getTopLevelMember("Net").getMember("LDAP").getAMethodCall(["open"])
41+
}
42+
}
43+
44+
/** A call to ` Net::LDAP::Filter.eq`, considered as a LDAP construction. */
45+
private class NetLdapConstruction extends LdapConstruction::Range, DataFlow::CallNode {
46+
DataFlow::Node query;
47+
48+
NetLdapConstruction() {
49+
this =
50+
API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter").getAMethodCall(["eq"]) and
51+
query = this.getArgument([0, 1])
52+
}
53+
54+
override DataFlow::Node getQuery() { result = query }
55+
}
56+
57+
/** A call considered as a LDAP execution. */
58+
private class NetLdapExecution extends LdapExecution::Range, DataFlow::CallNode {
59+
DataFlow::Node query;
60+
61+
NetLdapExecution() {
62+
// detecta cuando la query es una string pej
63+
// ldap.search(base: "ou=#{name},dc=example,dc=com"
64+
exists(LdapConnection ldapConnection |
65+
this = ldapConnection.getAMethodCall("search") and
66+
query = this.getKeywordArgument(_)
67+
)
68+
or
69+
// ignora esta parte
70+
exists(LdapConnection ldapConnection, NetLdapConstruction ldapConstruction |
71+
this = ldapConnection.getAMethodCall("search") and
72+
ldapConstruction = this.getKeywordArgument(_) and
73+
query = ldapConstruction
74+
)
75+
}
76+
77+
override DataFlow::Node getQuery() { result = query }
78+
}
79+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for detecting
3+
* LDAP Injections, as well as extension points for adding your own
4+
*/
5+
6+
private import codeql.ruby.Concepts
7+
private import codeql.ruby.DataFlow
8+
private import codeql.ruby.dataflow.BarrierGuards
9+
private import codeql.ruby.dataflow.RemoteFlowSources
10+
private import codeql.ruby.ApiGraphs
11+
private import codeql.ruby.dataflow.FlowSummary
12+
private import codeql.ruby.CFG
13+
14+
/**
15+
* Provides default sources, sinks and sanitizers for detecting
16+
* LDAP Injections, as well as extension points for adding your own
17+
*/
18+
module LdapInjection {
19+
/** A data flow source for LDAP Injection vulnerabilities */
20+
abstract class Source extends DataFlow::Node { }
21+
22+
/** A data flow sink for LDAP Injection vulnerabilities */
23+
abstract class Sink extends DataFlow::Node { }
24+
25+
/** A sanitizer for LDAP Injection vulnerabilities. */
26+
abstract class Sanitizer extends DataFlow::Node { }
27+
28+
/**
29+
* Additional taint steps for "LDAP Injection" vulnerabilities.
30+
*/
31+
predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
32+
exists(API::Node n, API::Node n2 |
33+
n = API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter")
34+
|
35+
n2 = API::getTopLevelMember("Net").getMember("LDAP") and
36+
nodeTo = n2.getAMethodCall(["new"]).getAMethodCall(["search"]) and
37+
nodeFrom = n.getAMethodCall(["eq"]).getArgument([0, 1])
38+
)
39+
}
40+
41+
/**
42+
* A source of remote user input, considered as a flow source.
43+
*/
44+
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
45+
46+
/**
47+
* An LDAP query execution considered as a flow sink.
48+
*/
49+
private class LdapExecutionAsSink extends Sink {
50+
LdapExecutionAsSink() { this = any(LdapExecution l).getQuery() }
51+
}
52+
53+
/**
54+
* A comparison with a constant string, considered as a sanitizer-guard.
55+
*/
56+
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
57+
58+
/**
59+
* An inclusion check against an array of constant strings, considered as a
60+
* sanitizer-guard.
61+
*/
62+
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
63+
StringConstArrayInclusionCallBarrier { }
64+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for detecting
3+
* LDAP Injections, as well as extension points for adding your own
4+
*/
5+
6+
private import codeql.ruby.DataFlow
7+
private import codeql.ruby.TaintTracking
8+
import LdapInjectionCustomizations
9+
import LdapInjectionCustomizations::LdapInjection
10+
11+
/**
12+
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
13+
*/
14+
class Configuration extends TaintTracking::Configuration {
15+
Configuration() { this = "LdapInjection" }
16+
17+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
18+
19+
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
20+
21+
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
22+
23+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
24+
LdapInjection::isAdditionalTaintStep(node1, node2)
25+
}
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name LDAP Injection
3+
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
4+
* malicious LDAP code by the user.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 9.8
8+
* @precision high
9+
* @id rb/ldap-injection
10+
* @tags security
11+
* external/cwe/cwe-090
12+
*/
13+
14+
import codeql.ruby.DataFlow
15+
import codeql.ruby.security.LdapInjectionQuery
16+
import DataFlow::PathGraph
17+
18+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
19+
where config.hasFlowPath(source, sink)
20+
select sink.getNode(), source, sink, "This LDAP query depends on a $@.", source.getNode(),
21+
"user-provided value"

0 commit comments

Comments
 (0)