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.
This commit is contained in:
Harry Maclean
2023-10-17 12:36:28 +01:00
parent 3ee425cc47
commit 081c1201ed

View File

@@ -15,6 +15,7 @@ import codeql.ruby.AST
import codeql.ruby.Concepts
import codeql.ruby.frameworks.ActionController
import codeql.ruby.frameworks.Gemfile
import codeql.ruby.DataFlow
/**
* Holds if a call to `protect_from_forgery` is made in the controller class `definedIn`,
@@ -28,23 +29,39 @@ private predicate protectFromForgeryCall(
}
/**
* Holds if the Gemfile for this application specifies a version of "rails" < 3.0.0.
* Rails versions from 3.0.0 onwards enable CSRF protection by default.
* Holds if the Gemfile for this application specifies a version of "rails" or "actionpack" < 5.2.
* Rails versions prior to 5.2 do not enable CSRF protection by default.
*/
private predicate railsPreVersion3() {
exists(Gemfile::Gem g | g.getName() = "rails" and g.getAVersionConstraint().before("5.2"))
private predicate railsPreVersion5_2() {
exists(Gemfile::Gem g |
g.getName() = ["rails", "actionpack"] and g.getAVersionConstraint().before("5.2")
)
}
private float getRailsConfigDefaultVersion() {
exists(DataFlow::CallNode config, DataFlow::CallNode loadDefaultsCall |
DataFlow::getConstant("Rails")
.getConstant("Application")
.getADescendentModule()
.getAnImmediateReference()
.flowsTo(config.getReceiver()) and
config.getMethodName() = "config" and
loadDefaultsCall.getReceiver() = config and
loadDefaultsCall.getMethodName() = "load_defaults" and
result = loadDefaultsCall.getArgument(0).getConstantValue().getFloat()
)
}
from ActionControllerClass c
where
not protectFromForgeryCall(_, c, _) and
// Rails versions prior to 3.0.0 require CSRF protection to be explicitly enabled.
// For later versions, there must exist a call to `csrf_meta_tags` in every HTML response.
// We currently just check for a call to this method anywhere in the codebase.
(
railsPreVersion3()
// Rails versions prior to 5.2 require CSRF protection to be explicitly enabled.
railsPreVersion5_2()
or
not any(MethodCall m).getMethodName() = ["csrf_meta_tags", "csrf_meta_tag"]
// For Rails >= 5.2, CSRF protection is enabled by default if there is a `load_defaults` call in the root application class
// which specifies a version >= 5.2.
not getRailsConfigDefaultVersion() >= 5.2
) and
// Only generate alerts for the topmost controller in the tree.
not exists(ActionControllerClass parent | c = parent.getAnImmediateDescendent())