Skip to content

Commit 01db73b

Browse files
authored
Merge pull request #5935 from porcupineyhairs/javaSstiNew
Java : Add SSTI query
2 parents fd83f3a + 7b425a8 commit 01db73b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1412
-1
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@Controller
2+
public class VelocitySSTI {
3+
4+
@GetMapping(value = "bad")
5+
public void bad(HttpServletRequest request) {
6+
Velocity.init();
7+
8+
String code = request.getParameter("code");
9+
10+
VelocityContext context = new VelocityContext();
11+
12+
context.put("name", "Velocity");
13+
context.put("project", "Jakarta");
14+
15+
StringWriter w = new StringWriter();
16+
// evaluate( Context context, Writer out, String logTag, String instring )
17+
Velocity.evaluate(context, w, "mystring", code);
18+
}
19+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@Controller
2+
public class VelocitySSTI {
3+
4+
@GetMapping(value = "good")
5+
public void good(HttpServletRequest request) {
6+
Velocity.init();
7+
VelocityContext context = new VelocityContext();
8+
9+
context.put("name", "Velocity");
10+
context.put("project", "Jakarta");
11+
12+
String s = "We are using $project $name to render this.";
13+
StringWriter w = new StringWriter();
14+
Velocity.evaluate(context, w, "mystring", s);
15+
System.out.println(" string : " + w);
16+
}
17+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 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. This permits the attacker to run arbitrary code in the server's context.</p>
7+
</overview>
8+
<recommendation>
9+
<p>
10+
To fix this, ensure that an untrusted value is not used as a template. If the application requirements do not allow this, use a sandboxed environment where access to unsafe attributes and methods is prohibited.
11+
</p>
12+
</recommendation>
13+
<example>
14+
<p>
15+
In the example given below, an untrusted HTTP parameter
16+
<code>code</code>
17+
is used as a Velocity template string. This can lead to remote code execution.
18+
</p>
19+
<sample src="SSTIBad.java" />
20+
21+
<p>
22+
In the next example the problem is avoided by using a fixed template string
23+
<code>s</code>
24+
. Since, the template is not attacker controlled in this case, we prevent untrusted code execution.
25+
</p>
26+
<sample src="SSTIGood.java" />
27+
</example>
28+
<references>
29+
<li>Portswigger : [Server Side Template Injection](https://portswigger.net/web-security/server-side-template-injection)</li>
30+
</references>
31+
</qhelp>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @name Server Side Template Injection
3+
* @description Untrusted input used as a template parameter can lead to remote code execution.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id java/server-side-template-injection
8+
* @tags security
9+
* external/cwe/cwe-094
10+
*/
11+
12+
import java
13+
import TemplateInjection
14+
import DataFlow::PathGraph
15+
16+
from TemplateInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
17+
where config.hasFlowPath(source, sink)
18+
select sink.getNode(), source, sink, "Potential arbitrary code execution due to $@.",
19+
source.getNode(), "a template value loaded from a remote source."
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/** Definitions related to the Server Side Template Injection (SSTI) query. */
2+
3+
import java
4+
import semmle.code.java.dataflow.TaintTracking
5+
import semmle.code.java.dataflow.FlowSources
6+
import experimental.semmle.code.java.frameworks.FreeMarker
7+
import experimental.semmle.code.java.frameworks.Velocity
8+
import experimental.semmle.code.java.frameworks.JinJava
9+
import experimental.semmle.code.java.frameworks.Pebble
10+
import experimental.semmle.code.java.frameworks.Thymeleaf
11+
12+
/** A taint tracking configuration to reason about Server Side Template Injection (SSTI) vulnerabilities */
13+
class TemplateInjectionFlowConfig extends TaintTracking::Configuration {
14+
TemplateInjectionFlowConfig() { this = "TemplateInjectionFlowConfig" }
15+
16+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
17+
18+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
19+
20+
override predicate isSanitizer(DataFlow::Node node) {
21+
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
22+
}
23+
24+
override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) {
25+
exists(AdditionalFlowStep a | a.isAdditionalTaintStep(prev, succ))
26+
}
27+
}
28+
29+
/**
30+
* A data flow sink for Server Side Template Injection (SSTI) vulnerabilities
31+
*/
32+
abstract private class Sink extends DataFlow::ExprNode { }
33+
34+
/**
35+
* A data flow step for Server Side Template Injection (SSTI) vulnerabilities
36+
*/
37+
private class AdditionalFlowStep extends Unit {
38+
abstract predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ);
39+
}
40+
41+
/**
42+
* An argument to FreeMarker template engine's `process` method call.
43+
*/
44+
private class FreeMarkerProcessSink extends Sink {
45+
FreeMarkerProcessSink() {
46+
exists(MethodAccess m |
47+
m.getCallee() instanceof MethodFreeMarkerTemplateProcess and
48+
m.getArgument(0) = this.getExpr()
49+
)
50+
}
51+
}
52+
53+
/**
54+
* An reader passed an argument to FreeMarker template engine's `Template`
55+
* construtor call.
56+
*/
57+
private class FreeMarkerConstructorSink extends Sink {
58+
FreeMarkerConstructorSink() {
59+
// Template(java.lang.String name, java.io.Reader reader)
60+
// Template(java.lang.String name, java.io.Reader reader, Configuration cfg)
61+
// Template(java.lang.String name, java.io.Reader reader, Configuration cfg, java.lang.String encoding)
62+
// Template(java.lang.String name, java.lang.String sourceName, java.io.Reader reader, Configuration cfg)
63+
// Template(java.lang.String name, java.lang.String sourceName, java.io.Reader reader, Configuration cfg, ParserConfiguration customParserConfiguration, java.lang.String encoding)
64+
// Template(java.lang.String name, java.lang.String sourceName, java.io.Reader reader, Configuration cfg, java.lang.String encoding)
65+
exists(ConstructorCall cc, Expr e |
66+
cc.getConstructor().getDeclaringType() instanceof TypeFreeMarkerTemplate and
67+
e = cc.getAnArgument() and
68+
(
69+
e.getType().(RefType).hasQualifiedName("java.io", "Reader") and
70+
this.asExpr() = e
71+
)
72+
)
73+
or
74+
exists(ConstructorCall cc |
75+
cc.getConstructor().getDeclaringType() instanceof TypeFreeMarkerTemplate and
76+
// Template(java.lang.String name, java.lang.String sourceCode, Configuration cfg)
77+
cc.getNumArgument() = 3 and
78+
cc.getArgument(1).getType() instanceof TypeString and
79+
this.asExpr() = cc.getArgument(1)
80+
)
81+
}
82+
}
83+
84+
/**
85+
* An argument to FreeMarker template engine's `putTemplate` method call.
86+
*/
87+
private class FreeMarkerStringTemplateLoaderPutTemplateSink extends Sink {
88+
FreeMarkerStringTemplateLoaderPutTemplateSink() {
89+
exists(MethodAccess ma |
90+
this.asExpr() = ma.getArgument(1) and
91+
ma.getMethod() instanceof MethodFreeMarkerStringTemplateLoaderPutTemplate
92+
)
93+
}
94+
}
95+
96+
/**
97+
* An argument to Pebble template engine's `getLiteralTemplate` or `getTemplate` method call.
98+
*/
99+
private class PebbleGetTemplateSinkTemplateSink extends Sink {
100+
PebbleGetTemplateSinkTemplateSink() {
101+
exists(MethodAccess ma |
102+
this.asExpr() = ma.getArgument(0) and
103+
ma.getMethod() instanceof MethodPebbleGetTemplate
104+
)
105+
}
106+
}
107+
108+
/**
109+
* An argument to JinJava template engine's `render` or `renderForResult` method call.
110+
*/
111+
private class JinjavaRenderSink extends Sink {
112+
JinjavaRenderSink() {
113+
exists(MethodAccess ma |
114+
this.asExpr() = ma.getArgument(0) and
115+
(
116+
ma.getMethod() instanceof MethodJinjavaRenderForResult
117+
or
118+
ma.getMethod() instanceof MethodJinjavaRender
119+
)
120+
)
121+
}
122+
}
123+
124+
/**
125+
* An argument to ThymeLeaf template engine's `process` method call.
126+
*/
127+
private class ThymeLeafRenderSink extends Sink {
128+
ThymeLeafRenderSink() {
129+
exists(MethodAccess ma |
130+
this.asExpr() = ma.getArgument(0) and
131+
ma.getMethod() instanceof MethodThymeleafProcess
132+
)
133+
}
134+
}
135+
136+
/**
137+
* Tainted data flowing into a Velocity Context through `put` method taints the context.
138+
*/
139+
private class VelocityContextFlow extends AdditionalFlowStep {
140+
override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) {
141+
exists(MethodAccess m | m.getMethod() instanceof MethodVelocityContextPut |
142+
m.getArgument(1) = prev.asExpr() and
143+
succ.asExpr() = m.getQualifier()
144+
)
145+
}
146+
}
147+
148+
/**
149+
* An argument to Velocity template engine's `mergeTemplate` method call.
150+
*/
151+
private class VelocityMergeTempSink extends Sink {
152+
VelocityMergeTempSink() {
153+
exists(MethodAccess m |
154+
// static boolean mergeTemplate(String templateName, String encoding, Context context, Writer writer)
155+
m.getCallee() instanceof MethodVelocityMergeTemplate and
156+
m.getArgument(2) = this.getExpr()
157+
)
158+
}
159+
}
160+
161+
/**
162+
* An argument to Velocity template engine's `mergeTemplate` method call.
163+
*/
164+
private class VelocityMergeSink extends Sink {
165+
VelocityMergeSink() {
166+
exists(MethodAccess m |
167+
m.getCallee() instanceof MethodVelocityMerge and
168+
// public void merge(Context context, Writer writer)
169+
// public void merge(Context context, Writer writer, List<String> macroLibraries)
170+
m.getArgument(0) = this.getExpr()
171+
)
172+
}
173+
}
174+
175+
/**
176+
* An argument to Velocity template engine's `evaluate` method call.
177+
*/
178+
private class VelocityEvaluateSink extends Sink {
179+
VelocityEvaluateSink() {
180+
exists(MethodAccess m |
181+
m.getCallee() instanceof MethodVelocityEvaluate and
182+
m.getArgument([0, 3]) = this.getExpr()
183+
)
184+
}
185+
}
186+
187+
/**
188+
* An argument to Velocity template engine's `parse` method call.
189+
*/
190+
private class VelocityParseSink extends Sink {
191+
VelocityParseSink() {
192+
exists(MethodAccess ma |
193+
this.asExpr() = ma.getArgument(0) and
194+
ma.getMethod() instanceof MethodVelocityParse
195+
)
196+
}
197+
}
198+
199+
/**
200+
* An argument to Velocity template engine's `putStringResource` method call.
201+
*/
202+
private class VelocityPutStringResSink extends Sink {
203+
VelocityPutStringResSink() {
204+
exists(MethodAccess ma |
205+
this.asExpr() = ma.getArgument(1) and
206+
ma.getMethod() instanceof MethodVelocityPutStringResource
207+
)
208+
}
209+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** Definitions related to the FreeMarker Templating library. */
2+
3+
import java
4+
5+
/** The `Template` class of the FreeMarker Template Engine */
6+
class TypeFreeMarkerTemplate extends Class {
7+
TypeFreeMarkerTemplate() { this.hasQualifiedName("freemarker.template", "Template") }
8+
}
9+
10+
/** The `process` method of the FreeMarker Template Engine's `Template` class */
11+
class MethodFreeMarkerTemplateProcess extends Method {
12+
MethodFreeMarkerTemplateProcess() {
13+
this.getDeclaringType() instanceof TypeFreeMarkerTemplate and
14+
this.hasName("process")
15+
}
16+
}
17+
18+
/** The `StringTemplateLoader` class of the FreeMarker Template Engine */
19+
class TypeFreeMarkerStringLoader extends Class {
20+
TypeFreeMarkerStringLoader() { this.hasQualifiedName("freemarker.cache", "StringTemplateLoader") }
21+
}
22+
23+
/** The `process` method of the FreeMarker Template Engine's `StringTemplateLoader` class */
24+
class MethodFreeMarkerStringTemplateLoaderPutTemplate extends Method {
25+
MethodFreeMarkerStringTemplateLoaderPutTemplate() {
26+
this.getDeclaringType() instanceof TypeFreeMarkerStringLoader and
27+
this.hasName("putTemplate")
28+
}
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/** Definitions related to the Jinjava Templating library. */
2+
3+
import java
4+
5+
/** The `Jinjava` class of the Jinjava Templating Engine. */
6+
class TypeJinjava extends Class {
7+
TypeJinjava() { this.hasQualifiedName("com.hubspot.jinjava", "Jinjava") }
8+
}
9+
10+
/** The `render` method of the Jinjava Templating Engine. */
11+
class MethodJinjavaRender extends Method {
12+
MethodJinjavaRender() {
13+
this.getDeclaringType() instanceof TypeJinjava and
14+
this.hasName("render")
15+
}
16+
}
17+
18+
/** The `render` method of the Jinjava Templating Engine. */
19+
class MethodJinjavaRenderForResult extends Method {
20+
MethodJinjavaRenderForResult() {
21+
this.getDeclaringType() instanceof TypeJinjava and
22+
this.hasName("renderForResult")
23+
}
24+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/** Definitions related to the Pebble Templating library. */
2+
3+
import java
4+
5+
/** The `PebbleEngine` class of the Pebble Templating Engine. */
6+
class TypePebbleEngine extends Class {
7+
TypePebbleEngine() { this.hasQualifiedName("com.mitchellbosecke.pebble", "PebbleEngine") }
8+
}
9+
10+
/** The `getTemplate` method of the Pebble Templating Engine. */
11+
class MethodPebbleGetTemplate extends Method {
12+
MethodPebbleGetTemplate() {
13+
this.getDeclaringType() instanceof TypePebbleEngine and
14+
this.hasName(["getTemplate", "getLiteralTemplate"])
15+
}
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/** Definitions related to the Thymeleaf Templating library. */
2+
3+
import java
4+
5+
/**
6+
* A class implementing the `ITemplateEngine` interface of the Thymeleaf
7+
* Templating Engine such as the `TemplateEngine` class.
8+
*/
9+
class TypeThymeleafTemplateEngine extends Class {
10+
TypeThymeleafTemplateEngine() {
11+
this.hasQualifiedName("org.thymeleaf", "TemplateEngine")
12+
or
13+
exists(Type t | this.getASupertype*().extendsOrImplements(t) |
14+
t.hasName("org.thymeleaf.ITemplateEngine")
15+
)
16+
}
17+
}
18+
19+
/** The `process` or `processThrottled` method of the Thymeleaf Templating Engine. */
20+
class MethodThymeleafProcess extends Method {
21+
MethodThymeleafProcess() {
22+
this.getDeclaringType() instanceof TypeThymeleafTemplateEngine and
23+
this.hasName(["process", "processThrottled"])
24+
}
25+
}

0 commit comments

Comments
 (0)