Skip to content

Commit cb3ebee

Browse files
authored
Merge pull request #9696 from thiggy1342/experimental-strong-params
RB: Experimental strong params query
2 parents db41ce5 + 6cfde70 commit cb3ebee

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* Added a new experimental query, `rb/weak-params`, to detect cases when the rails strong parameters pattern isn't followed and values flow into persistent store writes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Directly checking request parameters without following a strong params
8+
pattern can lead to unintentional avenues for injection attacks.
9+
</p>
10+
</overview>
11+
<recommendation>
12+
<p>
13+
Instead of manually checking parameters from the `param` object, it is
14+
recommended that you follow the strong parameters pattern established in
15+
Rails: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html
16+
</p>
17+
<p>
18+
In the strong parameters pattern, you are able to specify required and allowed
19+
parameters for each action called by your controller methods. This acts as an
20+
additional layer of data validation before being passed along to other areas
21+
of your application, such as the model.
22+
</p>
23+
</recommendation>
24+
25+
<references>
26+
27+
</references>
28+
</qhelp>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @name Weak or direct parameter references are used
3+
* @description Directly checking request parameters without following a strong params pattern can lead to unintentional avenues for injection attacks.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 5.0
7+
* @precision medium
8+
* @id rb/weak-params
9+
* @tags security
10+
*/
11+
12+
import ruby
13+
import codeql.ruby.Concepts
14+
import codeql.ruby.DataFlow
15+
import codeql.ruby.TaintTracking
16+
import codeql.ruby.frameworks.ActionController
17+
import DataFlow::PathGraph
18+
19+
/**
20+
* A call to `request` in an ActionController controller class.
21+
* This probably refers to the incoming HTTP request object.
22+
*/
23+
class ActionControllerRequest extends DataFlow::Node {
24+
ActionControllerRequest() {
25+
exists(DataFlow::CallNode c |
26+
c.asExpr().getExpr().getEnclosingModule() instanceof ActionControllerControllerClass and
27+
c.getMethodName() = "request"
28+
|
29+
c.flowsTo(this)
30+
)
31+
}
32+
}
33+
34+
/**
35+
* A direct parameters reference that happens inside a controller class.
36+
*/
37+
class WeakParams extends DataFlow::CallNode {
38+
WeakParams() {
39+
this.getReceiver() instanceof ActionControllerRequest and
40+
this.getMethodName() =
41+
["path_parameters", "query_parameters", "request_parameters", "GET", "POST"]
42+
}
43+
}
44+
45+
/**
46+
* A Taint tracking config where the source is a weak params access in a controller and the sink
47+
* is a method call of a model class
48+
*/
49+
class Configuration extends TaintTracking::Configuration {
50+
Configuration() { this = "WeakParamsConfiguration" }
51+
52+
override predicate isSource(DataFlow::Node node) { node instanceof WeakParams }
53+
54+
// the sink is an instance of a Model class that receives a method call
55+
override predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() }
56+
}
57+
58+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
59+
where config.hasFlowPath(source, sink)
60+
select sink.getNode(), source, sink,
61+
"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"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
edges
2+
| WeakParams.rb:5:28:5:53 | call to request_parameters : | WeakParams.rb:5:28:5:59 | ...[...] |
3+
| WeakParams.rb:10:28:10:51 | call to query_parameters : | WeakParams.rb:10:28:10:57 | ...[...] |
4+
| WeakParams.rb:15:28:15:39 | call to POST : | WeakParams.rb:15:28:15:45 | ...[...] |
5+
| WeakParams.rb:20:28:20:38 | call to GET : | WeakParams.rb:20:28:20:44 | ...[...] |
6+
nodes
7+
| WeakParams.rb:5:28:5:53 | call to request_parameters : | semmle.label | call to request_parameters : |
8+
| WeakParams.rb:5:28:5:59 | ...[...] | semmle.label | ...[...] |
9+
| WeakParams.rb:10:28:10:51 | call to query_parameters : | semmle.label | call to query_parameters : |
10+
| WeakParams.rb:10:28:10:57 | ...[...] | semmle.label | ...[...] |
11+
| WeakParams.rb:15:28:15:39 | call to POST : | semmle.label | call to POST : |
12+
| WeakParams.rb:15:28:15:45 | ...[...] | semmle.label | ...[...] |
13+
| WeakParams.rb:20:28:20:38 | call to GET : | semmle.label | call to GET : |
14+
| WeakParams.rb:20:28:20:44 | ...[...] | semmle.label | ...[...] |
15+
subpaths
16+
#select
17+
| WeakParams.rb:5:28:5:59 | ...[...] | WeakParams.rb:5:28:5:53 | call to request_parameters : | WeakParams.rb:5:28:5:59 | ...[...] | 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 |
18+
| WeakParams.rb:10:28:10:57 | ...[...] | WeakParams.rb:10:28:10:51 | call to query_parameters : | WeakParams.rb:10:28:10:57 | ...[...] | 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 |
19+
| WeakParams.rb:15:28:15:45 | ...[...] | WeakParams.rb:15:28:15:39 | call to POST : | WeakParams.rb:15:28:15:45 | ...[...] | 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 |
20+
| WeakParams.rb:20:28:20:44 | ...[...] | WeakParams.rb:20:28:20:38 | call to GET : | WeakParams.rb:20:28:20:44 | ...[...] | 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 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/weak-params/WeakParams.ql
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
class TestController < ActionController::Base
2+
3+
# Should catch
4+
def create
5+
TestObject.create(foo: request.request_parameters[:foo])
6+
end
7+
8+
# Should catch
9+
def create_query
10+
TestObject.create(foo: request.query_parameters[:foo])
11+
end
12+
13+
# Should catch
14+
def update_unsafe
15+
TestObject.update(foo: request.POST[:foo])
16+
end
17+
18+
# Should catch
19+
def update_unsafe_get
20+
TestObject.update(foo: request.GET[:foo])
21+
end
22+
23+
# Should not catch
24+
def update
25+
TestObject.update(object_params)
26+
end
27+
28+
# strong params method
29+
def object_params
30+
params.require(:uuid).permit(:notes)
31+
end
32+
33+
# Should not catch
34+
def test_non_sink
35+
puts request.request_parameters
36+
end
37+
end
38+
39+
class TestObject < ActiveRecord::Base
40+
end

0 commit comments

Comments
 (0)