Skip to content

Commit be163cf

Browse files
authored
Merge pull request github#12311 from maikypedia/maikypedia/ruby-ssti
Ruby: Add Server Side Template Injection query
2 parents 17b3383 + 4b1171c commit be163cf

File tree

16 files changed

+496
-0
lines changed

16 files changed

+496
-0
lines changed

ruby/ql/lib/codeql/ruby/Concepts.qll

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,3 +1063,69 @@ module Cryptography {
10631063

10641064
class BlockMode = SC::BlockMode;
10651065
}
1066+
1067+
/**
1068+
* A data-flow node that constructs a template.
1069+
*
1070+
* Often, it is worthy of an alert if a template is constructed such that
1071+
* executing it would be a security risk.
1072+
*
1073+
* If it is important that the template is rendered, use `TemplateRendering`.
1074+
*
1075+
* Extend this class to refine existing API models. If you want to model new APIs,
1076+
* extend `TemplateConstruction::Range` instead.
1077+
*/
1078+
class TemplateConstruction extends DataFlow::Node instanceof TemplateConstruction::Range {
1079+
/** Gets the argument that specifies the template to be constructed. */
1080+
DataFlow::Node getTemplate() { result = super.getTemplate() }
1081+
}
1082+
1083+
/** Provides a class for modeling new template rendering APIs. */
1084+
module TemplateConstruction {
1085+
/**
1086+
* A data-flow node that constructs a template.
1087+
*
1088+
* Often, it is worthy of an alert if a template is constructed such that
1089+
* executing it would be a security risk.
1090+
*
1091+
* If it is important that the template is rendered, use `TemplateRendering`.
1092+
*
1093+
* Extend this class to model new APIs. If you want to refine existing API models,
1094+
* extend `TemplateConstruction` instead.
1095+
*/
1096+
abstract class Range extends DataFlow::Node {
1097+
/** Gets the argument that specifies the template to be constructed. */
1098+
abstract DataFlow::Node getTemplate();
1099+
}
1100+
}
1101+
1102+
/**
1103+
* A data-flow node that renders templates.
1104+
*
1105+
* If the context of interest is such that merely constructing a template
1106+
* would be valuable to report, consider using `TemplateConstruction`.
1107+
*
1108+
* Extend this class to refine existing API models. If you want to model new APIs,
1109+
* extend `TemplateRendering::Range` instead.
1110+
*/
1111+
class TemplateRendering extends DataFlow::Node instanceof TemplateRendering::Range {
1112+
/** Gets the argument that specifies the template to be rendered. */
1113+
DataFlow::Node getTemplate() { result = super.getTemplate() }
1114+
}
1115+
1116+
/** Provides a class for modeling new template rendering APIs. */
1117+
module TemplateRendering {
1118+
/**
1119+
* A data-flow node that renders templates.
1120+
*
1121+
* If the context of interest is such that merely constructing a template
1122+
* would be valuable to report, consider using `TemplateConstruction`.
1123+
*
1124+
* Extend this class to model new APIs. If you want to refine existing API models,
1125+
* extend `TemplateRendering` instead.
1126+
*/
1127+
abstract class Range extends DataFlow::Node {
1128+
/** Gets the argument that specifies the template to be rendered. */
1129+
abstract DataFlow::Node getTemplate();
1130+
}
1131+
}

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ private import codeql.ruby.frameworks.ActionDispatch
2727
private import codeql.ruby.frameworks.PosixSpawn
2828
private import codeql.ruby.frameworks.StringFormatters
2929
private import codeql.ruby.frameworks.Json
30+
private import codeql.ruby.frameworks.Erb
31+
private import codeql.ruby.frameworks.Slim
3032
private import codeql.ruby.frameworks.Sinatra
3133
private import codeql.ruby.frameworks.Twirp
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Provides templating for embedding Ruby code into text files, allowing dynamic content generation in web applications.
3+
*/
4+
5+
private import codeql.ruby.ApiGraphs
6+
private import codeql.ruby.dataflow.FlowSummary
7+
private import codeql.ruby.Concepts
8+
9+
/**
10+
* Provides templating for embedding Ruby code into text files, allowing dynamic content generation in web applications.
11+
*/
12+
module Erb {
13+
/**
14+
* Flow summary for `ERB.new`. This method wraps a template string, compiling it.
15+
*/
16+
private class TemplateSummary extends SummarizedCallable {
17+
TemplateSummary() { this = "ERB.new" }
18+
19+
override MethodCall getACall() { result = any(ErbTemplateNewCall c).asExpr().getExpr() }
20+
21+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
22+
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
23+
}
24+
}
25+
26+
/** A call to `ERB.new`, considered as a template construction. */
27+
private class ErbTemplateNewCall extends TemplateConstruction::Range, DataFlow::CallNode {
28+
ErbTemplateNewCall() { this = API::getTopLevelMember("ERB").getAnInstantiation() }
29+
30+
override DataFlow::Node getTemplate() { result = this.getArgument(0) }
31+
}
32+
33+
/** A call to `ERB.new(foo).result(binding)`, considered as a template rendering. */
34+
private class ErbTemplateRendering extends TemplateRendering::Range, DataFlow::CallNode {
35+
private DataFlow::Node template;
36+
37+
ErbTemplateRendering() {
38+
exists(ErbTemplateNewCall templateConstruction |
39+
this = templateConstruction.getAMethodCall("result") and
40+
template = templateConstruction.getTemplate()
41+
)
42+
}
43+
44+
override DataFlow::Node getTemplate() { result = template }
45+
}
46+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Provides templating for embedding Ruby code into text files, allowing dynamic content generation in web applications.
3+
*/
4+
5+
private import codeql.ruby.ApiGraphs
6+
private import codeql.ruby.dataflow.FlowSummary
7+
private import codeql.ruby.Concepts
8+
9+
/**
10+
* Provides templating for embedding Ruby code into text files, allowing dynamic content generation in web applications.
11+
*/
12+
module Slim {
13+
/** A call to `Slim::Template.new`, considered as a template construction. */
14+
private class SlimTemplateNewCall extends TemplateConstruction::Range, DataFlow::CallNode {
15+
SlimTemplateNewCall() {
16+
this = API::getTopLevelMember("Slim").getMember("Template").getAnInstantiation()
17+
}
18+
19+
override DataFlow::Node getTemplate() {
20+
result.asExpr().getExpr() =
21+
this.getBlock().(DataFlow::BlockNode).asCallableAstNode().getAStmt()
22+
}
23+
}
24+
25+
/** A call to `Slim::Template.new{ foo }.render`, considered as a template rendering */
26+
private class SlimTemplateRendering extends TemplateRendering::Range, DataFlow::CallNode {
27+
private DataFlow::Node template;
28+
29+
SlimTemplateRendering() {
30+
exists(SlimTemplateNewCall templateConstruction |
31+
this = templateConstruction.getAMethodCall("render") and
32+
template = templateConstruction.getTemplate()
33+
)
34+
}
35+
36+
override DataFlow::Node getTemplate() { result = template }
37+
}
38+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for detecting
3+
* ERB Server Side Template Injections, as well as extension points for adding your own
4+
*/
5+
6+
private import codeql.ruby.Concepts
7+
private import codeql.ruby.DataFlow
8+
private import codeql.ruby.dataflow.BarrierGuards
9+
private import codeql.ruby.dataflow.RemoteFlowSources
10+
11+
/**
12+
* Provides default sources, sinks and sanitizers for detecting
13+
* Server Side Template Injections, as well as extension points for adding your own
14+
*/
15+
module TemplateInjection {
16+
/** A data flow source for SSTI vulnerabilities */
17+
abstract class Source extends DataFlow::Node { }
18+
19+
/** A data flow sink for SSTI vulnerabilities */
20+
abstract class Sink extends DataFlow::Node { }
21+
22+
/** A sanitizer for SSTI vulnerabilities. */
23+
abstract class Sanitizer extends DataFlow::Node { }
24+
25+
/**
26+
* A source of remote user input, considered as a flow source.
27+
*/
28+
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
29+
30+
/**
31+
* An Server Side Template Injection rendering, considered as a flow sink.
32+
*/
33+
private class TemplateRenderingAsSink extends Sink {
34+
TemplateRenderingAsSink() { this = any(TemplateRendering e).getTemplate() }
35+
}
36+
37+
/**
38+
* A comparison with a constant string, considered as a sanitizer-guard.
39+
*/
40+
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
41+
42+
/**
43+
* An inclusion check against an array of constant strings, considered as a
44+
* sanitizer-guard.
45+
*/
46+
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
47+
StringConstArrayInclusionCallBarrier
48+
{ }
49+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for detecting
3+
* Server Side Template Injections, as well as extension points for adding your own
4+
*/
5+
6+
private import codeql.ruby.DataFlow
7+
private import codeql.ruby.TaintTracking
8+
import TemplateInjectionCustomizations::TemplateInjection
9+
10+
/**
11+
* A taint-tracking configuration for detecting Server Side Template Injections vulnerabilities.
12+
*/
13+
class Configuration extends TaintTracking::Configuration {
14+
Configuration() { this = "TemplateInjection" }
15+
16+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
17+
18+
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
19+
20+
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
21+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* Added a new experimental query, `rb/server-side-template-injection`, to detect cases where user input may be embedded into a template's code in an unsafe manner.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
Template Injection occurs when user input is embedded in a template's code in an unsafe manner.
6+
An attacker can use native template syntax to inject a malicious payload into a template, which is then executed server-side.
7+
This permits the attacker to run arbitrary code in the server's context.
8+
</p>
9+
</overview>
10+
11+
<recommendation>
12+
<p>
13+
To fix this, ensure that untrusted input is not used as part of a template's code. If the application requirements do not allow this,
14+
use a sandboxed environment where access to unsafe attributes and methods is prohibited.
15+
</p>
16+
</recommendation>
17+
18+
<example>
19+
<p>
20+
Consider the example given below, an untrusted HTTP parameter <code>name</code> is used to generate a template string. This can lead to remote code execution.
21+
</p>
22+
<sample src="examples/SSTIBad.rb" />
23+
24+
<p>
25+
Here we have fixed the problem by including ERB/Slim syntax in the string, then the user input will be rendered but not evaluated.
26+
</p>
27+
<sample src="examples/SSTIGood.rb" />
28+
</example>
29+
30+
<references>
31+
<li>
32+
Wikipedia: <a href="https://en.wikipedia.org/wiki/Code_injection#Server_Side_Template_Injection">Server Side Template Injection</a>.
33+
</li>
34+
<li>
35+
Portswigger : <a href="https://portswigger.net/web-security/server-side-template-injection">Server Side Template Injection</a>.
36+
</li>
37+
</references>
38+
</qhelp>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name Server-side template injection
3+
* @description Building a server-side template from user-controlled sources is vulnerable to
4+
* insertion of malicious code by the user.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 8.8
8+
* @precision high
9+
* @id rb/server-side-template-injection
10+
* @tags security
11+
* external/cwe/cwe-94
12+
*/
13+
14+
import codeql.ruby.DataFlow
15+
import codeql.ruby.security.TemplateInjectionQuery
16+
import DataFlow::PathGraph
17+
18+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
19+
where config.hasFlowPath(source, sink)
20+
select sink.getNode(), source, sink, "This template depends on a $@.", source.getNode(),
21+
"user-provided value"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'erb'
2+
require 'slim'
3+
4+
class BadERBController < ActionController::Base
5+
def some_request_handler
6+
name = params["name"]
7+
html_text = "
8+
<!DOCTYPE html><html><body>
9+
<h2>Hello %s </h2></body></html>
10+
" % name
11+
template = ERB.new(html_text).result(binding)
12+
end
13+
end
14+
15+
class BadSlimController < ActionController::Base
16+
def some_request_handler
17+
name = params["name"]
18+
html_text = "
19+
<!DOCTYPE html><html><body>
20+
<h2>Hello %s </h2></body></html>
21+
" % name
22+
Slim::Template.new{ html_text }.render
23+
end
24+
end

0 commit comments

Comments
 (0)