@@ -12,6 +12,7 @@ private import codeql.ruby.frameworks.ActiveRecord
12
12
private import codeql.ruby.frameworks.ActiveStorage
13
13
private import codeql.ruby.ast.internal.Module
14
14
private import codeql.ruby.ApiGraphs
15
+ private import codeql.ruby.security.CryptoAlgorithms
15
16
16
17
/**
17
18
* A reference to either `Rails::Railtie`, `Rails::Engine`, or `Rails::Application`.
@@ -45,87 +46,143 @@ private DataFlow::CallNode getAConfigureCallNode() {
45
46
}
46
47
47
48
/**
48
- * An access to a Rails config object.
49
+ * Classes representing accesses to the Rails config object.
49
50
*/
50
- private class ConfigSourceNode extends DataFlow:: LocalSourceNode {
51
- ConfigSourceNode ( ) {
52
- // `Foo < Rails::Application ... config ...`
53
- exists ( MethodCall configCall | this .asExpr ( ) .getExpr ( ) = configCall |
54
- configCall .getMethodName ( ) = "config" and
55
- configCall .getEnclosingModule ( ) instanceof RailtieClass
56
- )
57
- or
58
- // `Rails.application.config`
59
- this =
60
- API:: getTopLevelMember ( "Rails" )
61
- .getReturn ( "application" )
62
- .getReturn ( "config" )
63
- .getAnImmediateUse ( )
64
- or
65
- // `Rails.application.configure { ... config ... }`
66
- // `Rails::Application.configure { ... config ... }`
67
- exists ( DataFlow:: CallNode configureCallNode , Block block , MethodCall configCall |
68
- configCall = this .asExpr ( ) .getExpr ( )
69
- |
70
- configureCallNode = getAConfigureCallNode ( ) and
71
- block = configureCallNode .asExpr ( ) .getExpr ( ) .( MethodCall ) .getBlock ( ) and
72
- configCall .getParent + ( ) = block and
73
- configCall .getMethodName ( ) = "config"
74
- )
51
+ private module Config {
52
+ /**
53
+ * An access to a Rails config object.
54
+ */
55
+ private class SourceNode extends DataFlow:: LocalSourceNode {
56
+ SourceNode ( ) {
57
+ // `Foo < Rails::Application ... config ...`
58
+ exists ( MethodCall configCall | this .asExpr ( ) .getExpr ( ) = configCall |
59
+ configCall .getMethodName ( ) = "config" and
60
+ configCall .getEnclosingModule ( ) instanceof RailtieClass
61
+ )
62
+ or
63
+ // `Rails.application.config`
64
+ this =
65
+ API:: getTopLevelMember ( "Rails" )
66
+ .getReturn ( "application" )
67
+ .getReturn ( "config" )
68
+ .getAnImmediateUse ( )
69
+ or
70
+ // `Rails.application.configure { ... config ... }`
71
+ // `Rails::Application.configure { ... config ... }`
72
+ exists ( DataFlow:: CallNode configureCallNode , Block block , MethodCall configCall |
73
+ configCall = this .asExpr ( ) .getExpr ( )
74
+ |
75
+ configureCallNode = getAConfigureCallNode ( ) and
76
+ block = configureCallNode .asExpr ( ) .getExpr ( ) .( MethodCall ) .getBlock ( ) and
77
+ configCall .getParent + ( ) = block and
78
+ configCall .getMethodName ( ) = "config"
79
+ )
80
+ }
75
81
}
76
- }
77
82
78
- private class ConfigNode extends DataFlow:: Node {
79
- ConfigNode ( ) { exists ( ConfigSourceNode src | src .flowsTo ( this ) ) }
80
- }
81
-
82
- // A call where the Rails application config is the receiver
83
- private class CallAgainstConfig extends DataFlow:: CallNode {
84
- CallAgainstConfig ( ) { this .getReceiver ( ) instanceof ConfigNode }
83
+ /**
84
+ * A reference to the Rails config object.
85
+ */
86
+ class Node extends DataFlow:: Node {
87
+ Node ( ) { exists ( SourceNode src | src .flowsTo ( this ) ) }
88
+ }
85
89
86
- MethodCall getCall ( ) { result = this .asExpr ( ) .getExpr ( ) }
90
+ /**
91
+ * A reference to the ActionController config object.
92
+ */
93
+ class ActionControllerNode extends DataFlow:: Node {
94
+ ActionControllerNode ( ) {
95
+ exists ( DataFlow:: CallNode source |
96
+ source .getReceiver ( ) instanceof Node and
97
+ source .getMethodName ( ) = "action_controller"
98
+ |
99
+ source .flowsTo ( this )
100
+ )
101
+ }
102
+ }
87
103
88
- Block getBlock ( ) { result = this .getCall ( ) .getBlock ( ) }
104
+ /**
105
+ * A reference to the ActionDispatch config object.
106
+ */
107
+ class ActionDispatchNode extends DataFlow:: Node {
108
+ ActionDispatchNode ( ) {
109
+ exists ( DataFlow:: CallNode source |
110
+ source .getReceiver ( ) instanceof Node and
111
+ source .getMethodName ( ) = "action_dispatch"
112
+ |
113
+ source .flowsTo ( this )
114
+ )
115
+ }
116
+ }
89
117
}
90
118
91
- private class ActionControllerConfigNode extends DataFlow:: Node {
92
- ActionControllerConfigNode ( ) {
93
- exists ( CallAgainstConfig source | source .getCall ( ) .getMethodName ( ) = "action_controller" |
94
- source .flowsTo ( this )
119
+ /**
120
+ * Classes representing nodes that set a Rails configuration value.
121
+ */
122
+ private module Settings {
123
+ private predicate isInTestConfiguration ( Location loc ) {
124
+ loc .getFile ( ) .getRelativePath ( ) .matches ( "%test/%" ) or
125
+ loc .getFile ( ) .getStem ( ) = "test"
126
+ }
127
+
128
+ private DataFlow:: Node getTransitiveReceiver ( DataFlow:: CallNode c ) {
129
+ exists ( DataFlow:: Node recv |
130
+ recv = c .getReceiver ( ) and
131
+ (
132
+ result = recv
133
+ or
134
+ recv instanceof DataFlow:: CallNode and
135
+ result = getTransitiveReceiver ( recv )
136
+ )
95
137
)
96
138
}
97
- }
98
139
99
- /** Holds if `node` can contain `value`. */
100
- private predicate hasBooleanValue ( DataFlow:: Node node , boolean value ) {
101
- exists ( DataFlow:: LocalSourceNode literal |
102
- literal .asExpr ( ) .getExpr ( ) .( BooleanLiteral ) .getValue ( ) = value and
103
- literal .flowsTo ( node )
104
- )
105
- }
140
+ private class Setting extends DataFlow:: CallNode {
141
+ Setting ( ) {
142
+ // exclude some test configuration
143
+ not isInTestConfiguration ( this .getLocation ( ) ) and
144
+ getTransitiveReceiver ( this ) instanceof Config:: Node
145
+ }
146
+ }
147
+
148
+ private class LiteralSetting extends Setting {
149
+ Literal valueLiteral ;
106
150
107
- // `<actionControllerConfig>.allow_forgery_protection = <verificationSetting>`
108
- private DataFlow:: CallNode getAnAllowForgeryProtectionCall ( boolean verificationSetting ) {
109
- // exclude some test configuration
110
- not (
111
- result .getLocation ( ) .getFile ( ) .getRelativePath ( ) .matches ( "%test/%" ) or
112
- result .getLocation ( ) .getFile ( ) .getStem ( ) = "test"
113
- ) and
114
- result .getReceiver ( ) instanceof ActionControllerConfigNode and
115
- result .asExpr ( ) .getExpr ( ) .( MethodCall ) .getMethodName ( ) = "allow_forgery_protection=" and
116
- hasBooleanValue ( result .getArgument ( 0 ) , verificationSetting )
151
+ LiteralSetting ( ) {
152
+ exists ( DataFlow:: LocalSourceNode lsn |
153
+ lsn .asExpr ( ) .getExpr ( ) = valueLiteral and
154
+ lsn .flowsTo ( this .getArgument ( 0 ) )
155
+ )
156
+ }
157
+
158
+ string getValueText ( ) { result = valueLiteral .getValueText ( ) }
159
+
160
+ string getSettingString ( ) { result = this .getMethodName ( ) + this .getValueText ( ) }
161
+ }
162
+
163
+ /**
164
+ * A node that sets a boolean value.
165
+ */
166
+ class BooleanSetting extends LiteralSetting {
167
+ override BooleanLiteral valueLiteral ;
168
+
169
+ boolean getValue ( ) { result = valueLiteral .getValue ( ) }
170
+ }
117
171
}
118
172
119
173
/**
120
174
* A `DataFlow::Node` that may enable or disable Rails CSRF protection in
121
175
* production code.
122
176
*/
123
- private class AllowForgeryProtectionSetting extends CSRFProtectionSetting:: Range {
124
- private boolean verificationSetting ;
125
-
126
- AllowForgeryProtectionSetting ( ) { this = getAnAllowForgeryProtectionCall ( verificationSetting ) }
177
+ private class AllowForgeryProtectionSetting extends Settings:: BooleanSetting ,
178
+ CSRFProtectionSetting:: Range {
179
+ AllowForgeryProtectionSetting ( ) {
180
+ this .getReceiver ( ) instanceof Config:: ActionControllerNode and
181
+ this .getMethodName ( ) = "allow_forgery_protection="
182
+ }
127
183
128
- override boolean getVerificationSetting ( ) { result = verificationSetting }
184
+ override boolean getVerificationSetting ( ) { result = this . getValue ( ) }
129
185
}
186
+
130
187
// TODO: initialization hooks, e.g. before_configuration, after_initialize...
131
188
// TODO: initializers
0 commit comments