|
11 | 11 | */ |
12 | 12 |
|
13 | 13 | import ruby |
| 14 | +import codeql.ruby.Concepts |
14 | 15 | import codeql.ruby.DataFlow |
15 | 16 | import codeql.ruby.TaintTracking |
| 17 | +import codeql.ruby.frameworks.ActionController |
16 | 18 | import DataFlow::PathGraph |
17 | 19 |
|
18 | 20 | /** |
19 | | - * A direct parameters reference that happens outside of a strong params method but inside |
20 | | - * of a controller class |
| 21 | + * A call to `request` in an ActionController controller class. |
| 22 | + * This probably refers to the incoming HTTP request object. |
21 | 23 | */ |
22 | | -class WeakParams extends Expr { |
23 | | - WeakParams() { |
24 | | - ( |
25 | | - allParamsAccess(this) or |
26 | | - this instanceof ParamsReference |
27 | | - ) and |
28 | | - this.getEnclosingModule() instanceof ControllerClass and |
29 | | - not this.getEnclosingMethod() instanceof StrongParamsMethod |
| 24 | +class ActionControllerRequest extends DataFlow::Node { |
| 25 | + ActionControllerRequest() { |
| 26 | + exists(DataFlow::CallNode c | |
| 27 | + c.asExpr().getExpr().getEnclosingModule() instanceof ActionControllerControllerClass and |
| 28 | + c.getMethodName() = "request" |
| 29 | + | |
| 30 | + c.flowsTo(this) |
| 31 | + ) |
30 | 32 | } |
31 | 33 | } |
32 | 34 |
|
33 | 35 | /** |
34 | | - * A controller class, which extendsd `ApplicationController` |
35 | | - */ |
36 | | -class ControllerClass extends ModuleBase { |
37 | | - ControllerClass() { this.getModule().getSuperClass+().toString() = "ApplicationController" } |
38 | | -} |
39 | | - |
40 | | -/** |
41 | | - * A method that follows the strong params naming convention |
| 36 | + * A direct parameters reference that happens inside a controller class. |
42 | 37 | */ |
43 | | -class StrongParamsMethod extends Method { |
44 | | - StrongParamsMethod() { this.getName().regexpMatch(".*_params") } |
| 38 | +class WeakParams extends DataFlow::CallNode { |
| 39 | + WeakParams() { |
| 40 | + this.getReceiver() instanceof ActionControllerRequest and |
| 41 | + allParamsAccess(this.asExpr().getExpr()) |
| 42 | + } |
45 | 43 | } |
46 | 44 |
|
47 | 45 | /** |
48 | 46 | * Holds call to a method that exposes or accesses all parameters from an inbound HTTP request |
49 | 47 | */ |
50 | 48 | predicate allParamsAccess(MethodCall call) { |
51 | | - call.getMethodName() = "expose_all" or |
52 | | - call.getMethodName() = "original_hash" or |
53 | 49 | call.getMethodName() = "path_parametes" or |
54 | 50 | call.getMethodName() = "query_parameters" or |
55 | 51 | call.getMethodName() = "request_parameters" or |
56 | 52 | call.getMethodName() = "GET" or |
57 | 53 | call.getMethodName() = "POST" |
58 | 54 | } |
59 | 55 |
|
60 | | -/** |
61 | | - * A reference to an element in the `params` object |
62 | | - */ |
63 | | -class ParamsReference extends ElementReference { |
64 | | - ParamsReference() { this.getAChild().toString() = "params" } |
65 | | -} |
66 | | - |
67 | | -/** |
68 | | - * A Model or ViewModel classes with a base class of `ViewModel`, `ApplicationRecord` or includes `ActionModel::Model`, |
69 | | - * which are required to support the strong parameters pattern |
70 | | - */ |
71 | | -class ModelClass extends ModuleBase { |
72 | | - ModelClass() { |
73 | | - this.getModule().getSuperClass+().toString() = "ViewModel" or |
74 | | - this.getModule().getSuperClass+().toString() = "ApplicationRecord" or |
75 | | - this.getModule().getSuperClass+().getAnIncludedModule().toString() = "ActionModel::Model" |
76 | | - } |
77 | | -} |
78 | | - |
79 | | -/** |
80 | | - * A DataFlow::Node representation that corresponds to any argument passed into a method call |
81 | | - * where the receiver is an instance of ModelClass |
82 | | - */ |
83 | | -class ModelClassMethodArgument extends DataFlow::Node { |
84 | | - |
85 | | - ModelClassMethodArgument() { |
86 | | - exists( DataFlow::CallNode call | this = call.getArgument(_) | |
87 | | - call.getExprNode().getNode().getParent+() instanceof ModelClass ) |
88 | | - } |
89 | | -} |
90 | | - |
91 | 56 | /** |
92 | 57 | * A Taint tracking config where the source is a weak params access in a controller and the sink |
93 | 58 | * is a method call of a model class |
94 | 59 | */ |
95 | 60 | class Configuration extends TaintTracking::Configuration { |
96 | | - Configuration() { this = "Configuration" } |
| 61 | + Configuration() { this = "WeakParamsConfiguration" } |
97 | 62 |
|
98 | | - override predicate isSource(DataFlow::Node node) { node.asExpr().getNode() instanceof WeakParams } |
| 63 | + override predicate isSource(DataFlow::Node node) { node instanceof WeakParams } |
99 | 64 |
|
100 | 65 | // the sink is an instance of a Model class that receives a method call |
101 | | - override predicate isSink(DataFlow::Node node) { node instanceof ModelClassMethodArgument } |
| 66 | + override predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() } |
102 | 67 | } |
103 | 68 |
|
104 | 69 | from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink |
105 | 70 | where config.hasFlowPath(source, sink) |
106 | | -select sink.getNode().(ModelClassMethodArgument), source, sink, |
| 71 | +select sink.getNode(), source, sink, |
107 | 72 | "By exposing all keys in request parameters or by blindy accessing them, unintended parameters could be used and lead to mass-assignment or have other unexpected side-effects. It is safer to follow the 'strong parameters' pattern in Rails, which is outlined here: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html" |
0 commit comments