@@ -8,36 +8,20 @@ private import codeql.ruby.Concepts
88private import codeql.ruby.controlflow.CfgNodes
99private import codeql.ruby.DataFlow
1010private import codeql.ruby.dataflow.RemoteFlowSources
11- private import ActionController
11+ private import codeql.ruby.frameworks.internal.Rails
12+ private import codeql.ruby.frameworks.Rails
1213
1314/**
1415 * Holds if this AST node is in a context where `ActionView` methods are available.
1516 */
16- predicate inActionViewContext ( AstNode n ) {
17+ private predicate inActionViewContext ( AstNode n ) {
1718 // Within a template
1819 n .getLocation ( ) .getFile ( ) instanceof ErbFile
1920}
2021
21- /**
22- * A method call on a string to mark it as HTML safe for Rails.
23- * Strings marked as such will not be automatically escaped when inserted into
24- * HTML.
25- */
26- abstract class HtmlSafeCall extends MethodCall {
27- HtmlSafeCall ( ) { this .getMethodName ( ) = "html_safe" }
28- }
29-
30- // A call to `html_safe` from within a template.
31- private class ActionViewHtmlSafeCall extends HtmlSafeCall {
32- ActionViewHtmlSafeCall ( ) { inActionViewContext ( this ) }
33- }
34-
35- /**
36- * A call to a method named "html_escape", "html_escape_once", or "h".
37- */
38- abstract class HtmlEscapeCall extends MethodCall {
39- // "h" is aliased to "html_escape" in ActiveSupport
40- HtmlEscapeCall ( ) { this .getMethodName ( ) = [ "html_escape" , "html_escape_once" , "h" , "sanitize" ] }
22+ /** A call to `html_safe` from within a template. */
23+ private class ActionViewHtmlSafeCall extends HtmlSafeCallImpl {
24+ ActionViewHtmlSafeCall ( ) { this .getMethodName ( ) = "html_safe" and inActionViewContext ( this ) }
4125}
4226
4327/**
@@ -53,12 +37,16 @@ class RailsHtmlEscaping extends Escaping::Range, DataFlow::CallNode {
5337 override string getKind ( ) { result = Escaping:: getHtmlKind ( ) }
5438}
5539
56- // A call to `html_escape` from within a template.
57- private class ActionViewHtmlEscapeCall extends HtmlEscapeCall {
58- ActionViewHtmlEscapeCall ( ) { inActionViewContext ( this ) }
40+ /** A call to `html_escape` from within a template. */
41+ private class ActionViewHtmlEscapeCall extends HtmlEscapeCallImpl {
42+ ActionViewHtmlEscapeCall ( ) {
43+ // "h" is aliased to "html_escape" in ActiveSupport
44+ this .getMethodName ( ) = [ "html_escape" , "html_escape_once" , "h" , "sanitize" ] and
45+ inActionViewContext ( this )
46+ }
5947}
6048
61- // A call in a context where some commonly used `ActionView` methods are available.
49+ /** A call in a context where some commonly used `ActionView` methods are available. */
6250private class ActionViewContextCall extends MethodCall {
6351 ActionViewContextCall ( ) {
6452 this .getReceiver ( ) instanceof SelfVariableAccess and
@@ -76,51 +64,14 @@ class RawCall extends ActionViewContextCall {
7664 RawCall ( ) { this .getMethodName ( ) = "raw" }
7765}
7866
79- // A call to the `params` method within the context of a template.
80- private class ActionViewParamsCall extends ActionViewContextCall , ParamsCall { }
67+ /** A call to the `params` method within the context of a template. */
68+ private class ActionViewParamsCall extends ActionViewContextCall , ParamsCallImpl {
69+ ActionViewParamsCall ( ) { this .getMethodName ( ) = "params" }
70+ }
8171
8272// A call to the `cookies` method within the context of a template.
83- private class ActionViewCookiesCall extends ActionViewContextCall , CookiesCall { }
84-
85- /**
86- * A call to a `render` method that will populate the response body with the
87- * rendered content.
88- */
89- abstract class RenderCall extends MethodCall {
90- RenderCall ( ) { this .getMethodName ( ) = "render" }
91-
92- private Expr getTemplatePathArgument ( ) {
93- // TODO: support other ways of specifying paths (e.g. `file`)
94- result = [ this .getKeywordArgument ( [ "partial" , "template" , "action" ] ) , this .getArgument ( 0 ) ]
95- }
96-
97- private string getTemplatePathValue ( ) {
98- result = this .getTemplatePathArgument ( ) .getConstantValue ( ) .getStringlikeValue ( )
99- }
100-
101- // everything up to and including the final slash, but ignoring any leading slash
102- private string getSubPath ( ) {
103- result = this .getTemplatePathValue ( ) .regexpCapture ( "^/?(.*/)?(?:[^/]*?)$" , 1 )
104- }
105-
106- // everything after the final slash, or the whole string if there is no slash
107- private string getBaseName ( ) {
108- result = this .getTemplatePathValue ( ) .regexpCapture ( "^/?(?:.*/)?([^/]*?)$" , 1 )
109- }
110-
111- /**
112- * Gets the template file to be rendered by this call, if any.
113- */
114- ErbFile getTemplateFile ( ) {
115- result .getTemplateName ( ) = this .getBaseName ( ) and
116- result .getRelativePath ( ) .matches ( "%app/views/" + this .getSubPath ( ) + "%" )
117- }
118-
119- /**
120- * Get the local variables passed as context to the renderer
121- */
122- HashLiteral getLocals ( ) { result = this .getKeywordArgument ( "locals" ) }
123- // TODO: implicit renders in controller actions
73+ private class ActionViewCookiesCall extends ActionViewContextCall , CookiesCallImpl {
74+ ActionViewCookiesCall ( ) { this .getMethodName ( ) = "cookies" }
12475}
12576
12677/**
@@ -172,17 +123,14 @@ private class RenderCallAsHttpResponse extends DataFlow::CallNode, Http::Server:
172123}
173124
174125/** A call to the `render` method within the context of a template. */
175- private class ActionViewRenderCall extends RenderCall , ActionViewContextCall { }
176-
177- /**
178- * A render call that does not automatically set the HTTP response body.
179- */
180- abstract class RenderToCall extends MethodCall {
181- RenderToCall ( ) { this .getMethodName ( ) = [ "render_to_body" , "render_to_string" ] }
126+ private class ActionViewRenderCall extends ActionViewContextCall , RenderCallImpl {
127+ ActionViewRenderCall ( ) { this .getMethodName ( ) = "render" }
182128}
183129
184- // A call to `render_to` from within a template.
185- private class ActionViewRenderToCall extends ActionViewContextCall , RenderToCall { }
130+ /** A call to `render_to` from within a template. */
131+ private class ActionViewRenderToCall extends ActionViewContextCall , RenderToCallImpl {
132+ ActionViewRenderToCall ( ) { this .getMethodName ( ) = [ "render_to_body" , "render_to_string" ] }
133+ }
186134
187135/**
188136 * A call to the ActionView `link_to` helper method.
@@ -224,24 +172,26 @@ module ActionView {
224172 * Action view helper methods which are XSS sinks.
225173 */
226174 module Helpers {
175+ abstract private class RawHelperCallImpl extends MethodCall {
176+ abstract Expr getRawArgument ( ) ;
177+ }
178+
227179 /**
228180 * A call to an ActionView helper which renders its argument without escaping.
229181 * The argument should be treated as an XSS sink. In the documentation for
230182 * classes in this module, the vulnerable argument is named `x`.
231183 */
232- abstract class RawHelperCall extends MethodCall {
233- /**
234- * Get an argument which is rendered without escaping.
235- */
236- abstract Expr getRawArgument ( ) ;
184+ class RawHelperCall extends MethodCall instanceof RawHelperCallImpl {
185+ /** Gets an argument that is rendered without escaping. */
186+ Expr getRawArgument ( ) { result = super .getRawArgument ( ) }
237187 }
238188
239189 /**
240190 * `ActionView::Helpers::TextHelper#simple_format`.
241191 *
242192 * `simple_format(x, y, sanitize: false)`.
243193 */
244- private class SimpleFormat extends ActionViewContextCall , RawHelperCall {
194+ private class SimpleFormat extends ActionViewContextCall , RawHelperCallImpl {
245195 SimpleFormat ( ) {
246196 this .getMethodName ( ) = "simple_format" and
247197 this .getKeywordArgument ( "sanitize" ) .getConstantValue ( ) .isBoolean ( false )
@@ -255,7 +205,7 @@ module ActionView {
255205 *
256206 * `truncate(x, escape: false)`.
257207 */
258- private class Truncate extends ActionViewContextCall , RawHelperCall {
208+ private class Truncate extends ActionViewContextCall , RawHelperCallImpl {
259209 Truncate ( ) {
260210 this .getMethodName ( ) = "truncate" and
261211 this .getKeywordArgument ( "escape" ) .getConstantValue ( ) .isBoolean ( false )
@@ -269,7 +219,7 @@ module ActionView {
269219 *
270220 * `highlight(x, y, sanitize: false)`.
271221 */
272- private class Highlight extends ActionViewContextCall , RawHelperCall {
222+ private class Highlight extends ActionViewContextCall , RawHelperCallImpl {
273223 Highlight ( ) {
274224 this .getMethodName ( ) = "highlight" and
275225 this .getKeywordArgument ( "sanitize" ) .getConstantValue ( ) .isBoolean ( false )
@@ -283,7 +233,7 @@ module ActionView {
283233 *
284234 * `javascript_tag(x)`.
285235 */
286- private class JavascriptTag extends ActionViewContextCall , RawHelperCall {
236+ private class JavascriptTag extends ActionViewContextCall , RawHelperCallImpl {
287237 JavascriptTag ( ) { this .getMethodName ( ) = "javascript_tag" }
288238
289239 override Expr getRawArgument ( ) { result = this .getArgument ( 0 ) }
@@ -294,7 +244,7 @@ module ActionView {
294244 *
295245 * `content_tag(x, x, y, false)`.
296246 */
297- private class ContentTag extends ActionViewContextCall , RawHelperCall {
247+ private class ContentTag extends ActionViewContextCall , RawHelperCallImpl {
298248 ContentTag ( ) {
299249 this .getMethodName ( ) = "content_tag" and
300250 this .getArgument ( 3 ) .getConstantValue ( ) .isBoolean ( false )
@@ -308,7 +258,7 @@ module ActionView {
308258 *
309259 * `tag(x, x, y, false)`.
310260 */
311- private class Tag extends ActionViewContextCall , RawHelperCall {
261+ private class Tag extends ActionViewContextCall , RawHelperCallImpl {
312262 Tag ( ) {
313263 this .getMethodName ( ) = "tag" and
314264 this .getArgument ( 3 ) .getConstantValue ( ) .isBoolean ( false )
@@ -322,7 +272,7 @@ module ActionView {
322272 *
323273 * `tag.h1(x, escape: false)`.
324274 */
325- private class TagMethod extends MethodCall , RawHelperCall {
275+ private class TagMethod extends MethodCall , RawHelperCallImpl {
326276 TagMethod ( ) {
327277 inActionViewContext ( this ) and
328278 this .getReceiver ( ) .( MethodCall ) .getMethodName ( ) = "tag" and
0 commit comments