Skip to content

Commit 507a610

Browse files
Reorganise into Custimizations file + add some more sinks on ActiveRecord methods
1 parent a8aac31 commit 507a610

File tree

4 files changed

+92
-47
lines changed

4 files changed

+92
-47
lines changed

ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,3 +792,36 @@ class ActiveRecordScopeCallTarget extends AdditionalCallTarget {
792792
)
793793
}
794794
}
795+
796+
/** Sinks for the mass assignment query */
797+
private module MassAssignmentSinks {
798+
private import codeql.ruby.security.MassAssignmentCustomizations
799+
800+
/** A call to a method that sets attributes of an database record using a hash. */
801+
private class MassAssignmentCall extends MassAssignment::Sink {
802+
MassAssignmentCall() {
803+
exists(DataFlow::CallNode call, string name |
804+
(
805+
call = activeRecordBaseClass().getAMethodCall(name)
806+
or
807+
call instanceof ActiveRecordInstanceMethodCall and
808+
call.getMethodName() = name
809+
) and
810+
(
811+
name =
812+
[
813+
"build", "create", "create!", "create_with", "create_or_find_by",
814+
"create_or_find_by!", "find_or_create_by", "find_or_create_by!", "insert", "insert!",
815+
"insert_all", "insert_all!", "instantiate", "new", "update", "update!", "upsert",
816+
"upsert_all"
817+
] and
818+
this = call.getArgument(0)
819+
or
820+
// These methods have an optional first id parameter.
821+
name = ["update", "update!"] and
822+
this = call.getArgument(1)
823+
)
824+
)
825+
}
826+
}
827+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Provides default sources, sinks, sanitizers, and flow steps for
3+
* detecting insecure mass assignment, as well as extension points for adding your own.
4+
*/
5+
6+
private import codeql.ruby.AST
7+
private import codeql.ruby.DataFlow
8+
private import codeql.ruby.TaintTracking
9+
private import codeql.ruby.dataflow.RemoteFlowSources
10+
11+
/**
12+
* Provides default sources, sinks, sanitizers, and flow steps for
13+
* detecting insecure mass assignment, as well as extension points for adding your own.
14+
*/
15+
module MassAssignment {
16+
/**
17+
* A data flow source for user input used for mass assignment.
18+
*/
19+
abstract class Source extends DataFlow::Node { }
20+
21+
/**
22+
* A data flow sink for user input used for mass assignment.
23+
*/
24+
abstract class Sink extends DataFlow::Node { }
25+
26+
/**
27+
* A sanitizer for insecure mass assignment.
28+
*/
29+
abstract class Sanitizer extends DataFlow::Node { }
30+
31+
/**
32+
* A call that permits arbitrary parameters to be used for mass assignment.
33+
*/
34+
abstract class MassPermit extends DataFlow::Node {
35+
/** Gets the argument for the parameters to be permitted */
36+
abstract DataFlow::Node getParamsArgument();
37+
38+
/** Gets the result node of the permitted parameters. */
39+
abstract DataFlow::Node getPermittedParamsResult();
40+
}
41+
42+
private class RemoteSource extends Source instanceof RemoteFlowSource { }
43+
44+
private class PermitBangCall extends MassPermit instanceof DataFlow::CallNode {
45+
PermitBangCall() { this.asExpr().getExpr().(MethodCall).getMethodName() = "permit!" }
46+
47+
override DataFlow::Node getParamsArgument() { result = this.(DataFlow::CallNode).getReceiver() }
48+
49+
override DataFlow::Node getPermittedParamsResult() {
50+
result = this
51+
or
52+
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = this.getParamsArgument()
53+
}
54+
}
55+
}

ruby/ql/lib/codeql/ruby/security/MassAssignmentQuery.qll

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,7 @@ private import codeql.ruby.AST
66
private import codeql.ruby.DataFlow
77
private import codeql.ruby.TaintTracking
88
private import codeql.ruby.dataflow.RemoteFlowSources
9-
10-
/** Provides default sources and sinks for the mass assignment query. */
11-
module MassAssignment {
12-
/**
13-
* A data flow source for user input used for mass assignment.
14-
*/
15-
abstract class Source extends DataFlow::Node { }
16-
17-
/**
18-
* A data flow sink for user input used for mass assignment.
19-
*/
20-
abstract class Sink extends DataFlow::Node { }
21-
22-
/**
23-
* A call that permits arbitrary parameters to be used for mass assignment.
24-
*/
25-
abstract class MassPermit extends DataFlow::Node {
26-
/** Gets the argument for the parameters to be permitted */
27-
abstract DataFlow::Node getParamsArgument();
28-
29-
/** Gets the result node of the permitted parameters. */
30-
abstract DataFlow::Node getPermittedParamsResult();
31-
}
32-
33-
private class RemoteSource extends Source instanceof RemoteFlowSource { }
34-
35-
private class CreateSink extends Sink {
36-
CreateSink() {
37-
exists(DataFlow::CallNode create |
38-
create.asExpr().getExpr().(MethodCall).getMethodName() = ["create", "new", "update"] and
39-
this = create.getArgument(0)
40-
)
41-
}
42-
}
43-
44-
private class PermitCall extends MassPermit instanceof DataFlow::CallNode {
45-
PermitCall() { this.asExpr().getExpr().(MethodCall).getMethodName() = "permit!" }
46-
47-
override DataFlow::Node getParamsArgument() { result = this.(DataFlow::CallNode).getReceiver() }
48-
49-
override DataFlow::Node getPermittedParamsResult() { result = this }
50-
}
51-
}
9+
private import MassAssignmentCustomizations
5210

5311
private module FlowState {
5412
private newtype TState =
@@ -78,16 +36,15 @@ private module Config implements DataFlow::StateConfigSig {
7836
predicate isSource(DataFlow::Node node, FlowState state) {
7937
node instanceof MassAssignment::Source and
8038
state instanceof FlowState::Unpermitted
81-
// or
82-
// node = any(MassAssignment::MassPermit p).getPermittedParamsResult() and
83-
// state instanceof FlowState::Permitted
8439
}
8540

8641
predicate isSink(DataFlow::Node node, FlowState state) {
8742
node instanceof MassAssignment::Sink and
8843
state instanceof FlowState::Permitted
8944
}
9045

46+
predicate isBarrier(DataFlow::Node node) { node instanceof MassAssignment::Sanitizer }
47+
9148
predicate isAdditionalFlowStep(
9249
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
9350
) {

ruby/ql/test/query-tests/security/cwe-915/MassAssignment.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ nodes
99
| test.rb:17:9:17:37 | call to permit! | semmle.label | call to permit! |
1010
subpaths
1111
#select
12-
| test.rb:8:18:8:28 | call to user_params | test.rb:17:9:17:14 | call to params | test.rb:8:18:8:28 | call to user_params | mass assignment |
12+
| test.rb:8:18:8:28 | call to user_params | test.rb:17:9:17:14 | call to params | test.rb:8:18:8:28 | call to user_params | This mass assignment operation can assign user-controlled attributes from $@. | test.rb:17:9:17:14 | call to params | this remote flow source |

0 commit comments

Comments
 (0)