mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Ruby: Tidy Rails.qll to make adding new settings modeling easier
This commit is contained in:
@@ -12,6 +12,7 @@ private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.security.CryptoAlgorithms
|
||||
|
||||
/**
|
||||
* A reference to either `Rails::Railtie`, `Rails::Engine`, or `Rails::Application`.
|
||||
@@ -45,87 +46,143 @@ private DataFlow::CallNode getAConfigureCallNode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a Rails config object.
|
||||
* Classes representing accesses to the Rails config object.
|
||||
*/
|
||||
private class ConfigSourceNode extends DataFlow::LocalSourceNode {
|
||||
ConfigSourceNode() {
|
||||
// `Foo < Rails::Application ... config ...`
|
||||
exists(MethodCall configCall | this.asExpr().getExpr() = configCall |
|
||||
configCall.getMethodName() = "config" and
|
||||
configCall.getEnclosingModule() instanceof RailtieClass
|
||||
)
|
||||
or
|
||||
// `Rails.application.config`
|
||||
this =
|
||||
API::getTopLevelMember("Rails")
|
||||
.getReturn("application")
|
||||
.getReturn("config")
|
||||
.getAnImmediateUse()
|
||||
or
|
||||
// `Rails.application.configure { ... config ... }`
|
||||
// `Rails::Application.configure { ... config ... }`
|
||||
exists(DataFlow::CallNode configureCallNode, Block block, MethodCall configCall |
|
||||
configCall = this.asExpr().getExpr()
|
||||
|
|
||||
configureCallNode = getAConfigureCallNode() and
|
||||
block = configureCallNode.asExpr().getExpr().(MethodCall).getBlock() and
|
||||
configCall.getParent+() = block and
|
||||
configCall.getMethodName() = "config"
|
||||
)
|
||||
private module Config {
|
||||
/**
|
||||
* An access to a Rails config object.
|
||||
*/
|
||||
private class SourceNode extends DataFlow::LocalSourceNode {
|
||||
SourceNode() {
|
||||
// `Foo < Rails::Application ... config ...`
|
||||
exists(MethodCall configCall | this.asExpr().getExpr() = configCall |
|
||||
configCall.getMethodName() = "config" and
|
||||
configCall.getEnclosingModule() instanceof RailtieClass
|
||||
)
|
||||
or
|
||||
// `Rails.application.config`
|
||||
this =
|
||||
API::getTopLevelMember("Rails")
|
||||
.getReturn("application")
|
||||
.getReturn("config")
|
||||
.getAnImmediateUse()
|
||||
or
|
||||
// `Rails.application.configure { ... config ... }`
|
||||
// `Rails::Application.configure { ... config ... }`
|
||||
exists(DataFlow::CallNode configureCallNode, Block block, MethodCall configCall |
|
||||
configCall = this.asExpr().getExpr()
|
||||
|
|
||||
configureCallNode = getAConfigureCallNode() and
|
||||
block = configureCallNode.asExpr().getExpr().(MethodCall).getBlock() and
|
||||
configCall.getParent+() = block and
|
||||
configCall.getMethodName() = "config"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the Rails config object.
|
||||
*/
|
||||
class Node extends DataFlow::Node {
|
||||
Node() { exists(SourceNode src | src.flowsTo(this)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the ActionController config object.
|
||||
*/
|
||||
class ActionControllerNode extends DataFlow::Node {
|
||||
ActionControllerNode() {
|
||||
exists(DataFlow::CallNode source |
|
||||
source.getReceiver() instanceof Node and
|
||||
source.getMethodName() = "action_controller"
|
||||
|
|
||||
source.flowsTo(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the ActionDispatch config object.
|
||||
*/
|
||||
class ActionDispatchNode extends DataFlow::Node {
|
||||
ActionDispatchNode() {
|
||||
exists(DataFlow::CallNode source |
|
||||
source.getReceiver() instanceof Node and
|
||||
source.getMethodName() = "action_dispatch"
|
||||
|
|
||||
source.flowsTo(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfigNode extends DataFlow::Node {
|
||||
ConfigNode() { exists(ConfigSourceNode src | src.flowsTo(this)) }
|
||||
}
|
||||
/**
|
||||
* Classes representing nodes that set a Rails configuration value.
|
||||
*/
|
||||
private module Settings {
|
||||
private predicate isInTestConfiguration(Location loc) {
|
||||
loc.getFile().getRelativePath().matches("%test/%") or
|
||||
loc.getFile().getStem() = "test"
|
||||
}
|
||||
|
||||
// A call where the Rails application config is the receiver
|
||||
private class CallAgainstConfig extends DataFlow::CallNode {
|
||||
CallAgainstConfig() { this.getReceiver() instanceof ConfigNode }
|
||||
|
||||
MethodCall getCall() { result = this.asExpr().getExpr() }
|
||||
|
||||
Block getBlock() { result = this.getCall().getBlock() }
|
||||
}
|
||||
|
||||
private class ActionControllerConfigNode extends DataFlow::Node {
|
||||
ActionControllerConfigNode() {
|
||||
exists(CallAgainstConfig source | source.getCall().getMethodName() = "action_controller" |
|
||||
source.flowsTo(this)
|
||||
private DataFlow::Node getTransitiveReceiver(DataFlow::CallNode c) {
|
||||
exists(DataFlow::Node recv |
|
||||
recv = c.getReceiver() and
|
||||
(
|
||||
result = recv
|
||||
or
|
||||
recv instanceof DataFlow::CallNode and
|
||||
result = getTransitiveReceiver(recv)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `node` can contain `value`. */
|
||||
private predicate hasBooleanValue(DataFlow::Node node, boolean value) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().(BooleanLiteral).getValue() = value and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
private class Setting extends DataFlow::CallNode {
|
||||
Setting() {
|
||||
// exclude some test configuration
|
||||
not isInTestConfiguration(this.getLocation()) and
|
||||
getTransitiveReceiver(this) instanceof Config::Node
|
||||
}
|
||||
}
|
||||
|
||||
// `<actionControllerConfig>.allow_forgery_protection = <verificationSetting>`
|
||||
private DataFlow::CallNode getAnAllowForgeryProtectionCall(boolean verificationSetting) {
|
||||
// exclude some test configuration
|
||||
not (
|
||||
result.getLocation().getFile().getRelativePath().matches("%test/%") or
|
||||
result.getLocation().getFile().getStem() = "test"
|
||||
) and
|
||||
result.getReceiver() instanceof ActionControllerConfigNode and
|
||||
result.asExpr().getExpr().(MethodCall).getMethodName() = "allow_forgery_protection=" and
|
||||
hasBooleanValue(result.getArgument(0), verificationSetting)
|
||||
private class LiteralSetting extends Setting {
|
||||
Literal valueLiteral;
|
||||
|
||||
LiteralSetting() {
|
||||
exists(DataFlow::LocalSourceNode lsn |
|
||||
lsn.asExpr().getExpr() = valueLiteral and
|
||||
lsn.flowsTo(this.getArgument(0))
|
||||
)
|
||||
}
|
||||
|
||||
string getValueText() { result = valueLiteral.getValueText() }
|
||||
|
||||
string getSettingString() { result = this.getMethodName() + this.getValueText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that sets a boolean value.
|
||||
*/
|
||||
class BooleanSetting extends LiteralSetting {
|
||||
override BooleanLiteral valueLiteral;
|
||||
|
||||
boolean getValue() { result = valueLiteral.getValue() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that may enable or disable Rails CSRF protection in
|
||||
* production code.
|
||||
*/
|
||||
private class AllowForgeryProtectionSetting extends CSRFProtectionSetting::Range {
|
||||
private boolean verificationSetting;
|
||||
private class AllowForgeryProtectionSetting extends Settings::BooleanSetting,
|
||||
CSRFProtectionSetting::Range {
|
||||
AllowForgeryProtectionSetting() {
|
||||
this.getReceiver() instanceof Config::ActionControllerNode and
|
||||
this.getMethodName() = "allow_forgery_protection="
|
||||
}
|
||||
|
||||
AllowForgeryProtectionSetting() { this = getAnAllowForgeryProtectionCall(verificationSetting) }
|
||||
|
||||
override boolean getVerificationSetting() { result = verificationSetting }
|
||||
override boolean getVerificationSetting() { result = this.getValue() }
|
||||
}
|
||||
|
||||
// TODO: initialization hooks, e.g. before_configuration, after_initialize...
|
||||
// TODO: initializers
|
||||
|
||||
Reference in New Issue
Block a user