Skip to content

Commit 081c120

Browse files
committed
Ruby: Make csrf query more specific
CSRF protection only needs to be explicitly enabled on Rails applications < 5.2 _or_ those that don't include a `load_defaults` call with a version >= 5.2.
1 parent 3ee425c commit 081c120

File tree

1 file changed

+26
-9
lines changed

1 file changed

+26
-9
lines changed

ruby/ql/src/queries/security/cwe-352/CSRFProtectionNotEnabled.ql

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import codeql.ruby.AST
1515
import codeql.ruby.Concepts
1616
import codeql.ruby.frameworks.ActionController
1717
import codeql.ruby.frameworks.Gemfile
18+
import codeql.ruby.DataFlow
1819

1920
/**
2021
* Holds if a call to `protect_from_forgery` is made in the controller class `definedIn`,
@@ -28,23 +29,39 @@ private predicate protectFromForgeryCall(
2829
}
2930

3031
/**
31-
* Holds if the Gemfile for this application specifies a version of "rails" < 3.0.0.
32-
* Rails versions from 3.0.0 onwards enable CSRF protection by default.
32+
* Holds if the Gemfile for this application specifies a version of "rails" or "actionpack" < 5.2.
33+
* Rails versions prior to 5.2 do not enable CSRF protection by default.
3334
*/
34-
private predicate railsPreVersion3() {
35-
exists(Gemfile::Gem g | g.getName() = "rails" and g.getAVersionConstraint().before("5.2"))
35+
private predicate railsPreVersion5_2() {
36+
exists(Gemfile::Gem g |
37+
g.getName() = ["rails", "actionpack"] and g.getAVersionConstraint().before("5.2")
38+
)
39+
}
40+
41+
private float getRailsConfigDefaultVersion() {
42+
exists(DataFlow::CallNode config, DataFlow::CallNode loadDefaultsCall |
43+
DataFlow::getConstant("Rails")
44+
.getConstant("Application")
45+
.getADescendentModule()
46+
.getAnImmediateReference()
47+
.flowsTo(config.getReceiver()) and
48+
config.getMethodName() = "config" and
49+
loadDefaultsCall.getReceiver() = config and
50+
loadDefaultsCall.getMethodName() = "load_defaults" and
51+
result = loadDefaultsCall.getArgument(0).getConstantValue().getFloat()
52+
)
3653
}
3754

3855
from ActionControllerClass c
3956
where
4057
not protectFromForgeryCall(_, c, _) and
41-
// Rails versions prior to 3.0.0 require CSRF protection to be explicitly enabled.
42-
// For later versions, there must exist a call to `csrf_meta_tags` in every HTML response.
43-
// We currently just check for a call to this method anywhere in the codebase.
4458
(
45-
railsPreVersion3()
59+
// Rails versions prior to 5.2 require CSRF protection to be explicitly enabled.
60+
railsPreVersion5_2()
4661
or
47-
not any(MethodCall m).getMethodName() = ["csrf_meta_tags", "csrf_meta_tag"]
62+
// For Rails >= 5.2, CSRF protection is enabled by default if there is a `load_defaults` call in the root application class
63+
// which specifies a version >= 5.2.
64+
not getRailsConfigDefaultVersion() >= 5.2
4865
) and
4966
// Only generate alerts for the topmost controller in the tree.
5067
not exists(ActionControllerClass parent | c = parent.getAnImmediateDescendent())

0 commit comments

Comments
 (0)