-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Java : Add SSTI query #5935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Java : Add SSTI query #5935
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e536628
Java : Add SSTI query
c81d85f
Include suggestions from review
476997a
Replace more non-breaking spaces
smowton 50d9945
Autoformat
smowton a8fe10f
Java template injection query: import pathgraph
smowton 7b425a8
Note path query expectations
smowton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
java/ql/src/experimental/Security/CWE/CWE-094/SSTIBad.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
@Controller | ||
public class VelocitySSTI { | ||
|
||
@GetMapping(value = "bad") | ||
public void bad(HttpServletRequest request) { | ||
Velocity.init(); | ||
|
||
String code = request.getParameter("code"); | ||
|
||
VelocityContext context = new VelocityContext(); | ||
|
||
context.put("name", "Velocity"); | ||
context.put("project", "Jakarta"); | ||
|
||
StringWriter w = new StringWriter(); | ||
// evaluate( Context context, Writer out, String logTag, String instring ) | ||
Velocity.evaluate(context, w, "mystring", code); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
java/ql/src/experimental/Security/CWE/CWE-094/SSTIGood.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
@Controller | ||
public class VelocitySSTI { | ||
|
||
@GetMapping(value = "good") | ||
public void good(HttpServletRequest request) { | ||
Velocity.init(); | ||
VelocityContext context = new VelocityContext(); | ||
|
||
context.put("name", "Velocity"); | ||
context.put("project", "Jakarta"); | ||
|
||
String s = "We are using $project $name to render this."; | ||
StringWriter w = new StringWriter(); | ||
Velocity.evaluate(context, w, "mystring", s); | ||
System.out.println(" string : " + w); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.qhelp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p> | ||
Template Injection occurs when user input is embedded in a template in an unsafe manner. | ||
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> | ||
</overview> | ||
<recommendation> | ||
<p> | ||
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. | ||
</p> | ||
</recommendation> | ||
<example> | ||
<p> | ||
In the example given below, an untrusted HTTP parameter | ||
<code>code</code> | ||
is used as a Velocity template string. This can lead to remote code execution. | ||
</p> | ||
<sample src="SSTIBad.java" /> | ||
|
||
<p> | ||
In the next example the problem is avoided by using a fixed template string | ||
<code>s</code> | ||
. Since, the template is not attacker controlled in this case, we prevent untrusted code execution. | ||
</p> | ||
<sample src="SSTIGood.java" /> | ||
</example> | ||
<references> | ||
<li>Portswigger : [Server Side Template Injection](https://portswigger.net/web-security/server-side-template-injection)</li> | ||
</references> | ||
</qhelp> |
19 changes: 19 additions & 0 deletions
19
java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* @name Server Side Template Injection | ||
* @description Untrusted input used as a template parameter can lead to remote code execution. | ||
* @kind path-problem | ||
* @problem.severity error | ||
* @precision high | ||
* @id java/server-side-template-injection | ||
* @tags security | ||
* external/cwe/cwe-094 | ||
*/ | ||
|
||
import java | ||
import TemplateInjection | ||
import DataFlow::PathGraph | ||
|
||
from TemplateInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink | ||
where config.hasFlowPath(source, sink) | ||
select sink.getNode(), source, sink, "Potential arbitrary code execution due to $@.", | ||
source.getNode(), "a template value loaded from a remote source." |
209 changes: 209 additions & 0 deletions
209
java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
/** Definitions related to the Server Side Template Injection (SSTI) query. */ | ||
|
||
import java | ||
import semmle.code.java.dataflow.TaintTracking | ||
import semmle.code.java.dataflow.FlowSources | ||
import experimental.semmle.code.java.frameworks.FreeMarker | ||
import experimental.semmle.code.java.frameworks.Velocity | ||
import experimental.semmle.code.java.frameworks.JinJava | ||
import experimental.semmle.code.java.frameworks.Pebble | ||
import experimental.semmle.code.java.frameworks.Thymeleaf | ||
|
||
/** A taint tracking configuration to reason about Server Side Template Injection (SSTI) vulnerabilities */ | ||
class TemplateInjectionFlowConfig extends TaintTracking::Configuration { | ||
TemplateInjectionFlowConfig() { this = "TemplateInjectionFlowConfig" } | ||
|
||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } | ||
|
||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } | ||
|
||
override predicate isSanitizer(DataFlow::Node node) { | ||
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType | ||
} | ||
|
||
override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) { | ||
exists(AdditionalFlowStep a | a.isAdditionalTaintStep(prev, succ)) | ||
} | ||
} | ||
|
||
/** | ||
* A data flow sink for Server Side Template Injection (SSTI) vulnerabilities | ||
*/ | ||
abstract private class Sink extends DataFlow::ExprNode { } | ||
|
||
/** | ||
* A data flow step for Server Side Template Injection (SSTI) vulnerabilities | ||
*/ | ||
private class AdditionalFlowStep extends Unit { | ||
abstract predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ); | ||
} | ||
|
||
/** | ||
* An argument to FreeMarker template engine's `process` method call. | ||
*/ | ||
private class FreeMarkerProcessSink extends Sink { | ||
FreeMarkerProcessSink() { | ||
exists(MethodAccess m | | ||
m.getCallee() instanceof MethodFreeMarkerTemplateProcess and | ||
m.getArgument(0) = this.getExpr() | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An reader passed an argument to FreeMarker template engine's `Template` | ||
* construtor call. | ||
*/ | ||
private class FreeMarkerConstructorSink extends Sink { | ||
FreeMarkerConstructorSink() { | ||
// Template(java.lang.String name, java.io.Reader reader) | ||
// Template(java.lang.String name, java.io.Reader reader, Configuration cfg) | ||
// Template(java.lang.String name, java.io.Reader reader, Configuration cfg, java.lang.String encoding) | ||
// Template(java.lang.String name, java.lang.String sourceName, java.io.Reader reader, Configuration cfg) | ||
// Template(java.lang.String name, java.lang.String sourceName, java.io.Reader reader, Configuration cfg, ParserConfiguration customParserConfiguration, java.lang.String encoding) | ||
// Template(java.lang.String name, java.lang.String sourceName, java.io.Reader reader, Configuration cfg, java.lang.String encoding) | ||
exists(ConstructorCall cc, Expr e | | ||
cc.getConstructor().getDeclaringType() instanceof TypeFreeMarkerTemplate and | ||
e = cc.getAnArgument() and | ||
( | ||
e.getType().(RefType).hasQualifiedName("java.io", "Reader") and | ||
this.asExpr() = e | ||
) | ||
) | ||
or | ||
exists(ConstructorCall cc | | ||
cc.getConstructor().getDeclaringType() instanceof TypeFreeMarkerTemplate and | ||
// Template(java.lang.String name, java.lang.String sourceCode, Configuration cfg) | ||
cc.getNumArgument() = 3 and | ||
cc.getArgument(1).getType() instanceof TypeString and | ||
this.asExpr() = cc.getArgument(1) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to FreeMarker template engine's `putTemplate` method call. | ||
*/ | ||
private class FreeMarkerStringTemplateLoaderPutTemplateSink extends Sink { | ||
FreeMarkerStringTemplateLoaderPutTemplateSink() { | ||
exists(MethodAccess ma | | ||
this.asExpr() = ma.getArgument(1) and | ||
ma.getMethod() instanceof MethodFreeMarkerStringTemplateLoaderPutTemplate | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to Pebble template engine's `getLiteralTemplate` or `getTemplate` method call. | ||
*/ | ||
private class PebbleGetTemplateSinkTemplateSink extends Sink { | ||
PebbleGetTemplateSinkTemplateSink() { | ||
exists(MethodAccess ma | | ||
this.asExpr() = ma.getArgument(0) and | ||
ma.getMethod() instanceof MethodPebbleGetTemplate | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to JinJava template engine's `render` or `renderForResult` method call. | ||
*/ | ||
private class JinjavaRenderSink extends Sink { | ||
JinjavaRenderSink() { | ||
exists(MethodAccess ma | | ||
this.asExpr() = ma.getArgument(0) and | ||
( | ||
ma.getMethod() instanceof MethodJinjavaRenderForResult | ||
or | ||
ma.getMethod() instanceof MethodJinjavaRender | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to ThymeLeaf template engine's `process` method call. | ||
*/ | ||
private class ThymeLeafRenderSink extends Sink { | ||
ThymeLeafRenderSink() { | ||
exists(MethodAccess ma | | ||
this.asExpr() = ma.getArgument(0) and | ||
ma.getMethod() instanceof MethodThymeleafProcess | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* Tainted data flowing into a Velocity Context through `put` method taints the context. | ||
*/ | ||
private class VelocityContextFlow extends AdditionalFlowStep { | ||
This conversation was marked as resolved.
Show resolved
Hide resolved
|
||
override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) { | ||
exists(MethodAccess m | m.getMethod() instanceof MethodVelocityContextPut | | ||
m.getArgument(1) = prev.asExpr() and | ||
succ.asExpr() = m.getQualifier() | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to Velocity template engine's `mergeTemplate` method call. | ||
*/ | ||
private class VelocityMergeTempSink extends Sink { | ||
VelocityMergeTempSink() { | ||
exists(MethodAccess m | | ||
// static boolean mergeTemplate(String templateName, String encoding, Context context, Writer writer) | ||
m.getCallee() instanceof MethodVelocityMergeTemplate and | ||
m.getArgument(2) = this.getExpr() | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to Velocity template engine's `mergeTemplate` method call. | ||
*/ | ||
private class VelocityMergeSink extends Sink { | ||
VelocityMergeSink() { | ||
exists(MethodAccess m | | ||
m.getCallee() instanceof MethodVelocityMerge and | ||
// public void merge(Context context, Writer writer) | ||
// public void merge(Context context, Writer writer, List<String> macroLibraries) | ||
m.getArgument(0) = this.getExpr() | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to Velocity template engine's `evaluate` method call. | ||
*/ | ||
private class VelocityEvaluateSink extends Sink { | ||
VelocityEvaluateSink() { | ||
exists(MethodAccess m | | ||
m.getCallee() instanceof MethodVelocityEvaluate and | ||
m.getArgument([0, 3]) = this.getExpr() | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to Velocity template engine's `parse` method call. | ||
*/ | ||
private class VelocityParseSink extends Sink { | ||
VelocityParseSink() { | ||
exists(MethodAccess ma | | ||
this.asExpr() = ma.getArgument(0) and | ||
atorralba marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ma.getMethod() instanceof MethodVelocityParse | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* An argument to Velocity template engine's `putStringResource` method call. | ||
*/ | ||
private class VelocityPutStringResSink extends Sink { | ||
VelocityPutStringResSink() { | ||
exists(MethodAccess ma | | ||
this.asExpr() = ma.getArgument(1) and | ||
ma.getMethod() instanceof MethodVelocityPutStringResource | ||
) | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
java/ql/src/experimental/semmle/code/java/frameworks/FreeMarker.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** Definitions related to the FreeMarker Templating library. */ | ||
|
||
import java | ||
|
||
/** The `Template` class of the FreeMarker Template Engine */ | ||
class TypeFreeMarkerTemplate extends Class { | ||
TypeFreeMarkerTemplate() { this.hasQualifiedName("freemarker.template", "Template") } | ||
} | ||
|
||
/** The `process` method of the FreeMarker Template Engine's `Template` class */ | ||
class MethodFreeMarkerTemplateProcess extends Method { | ||
MethodFreeMarkerTemplateProcess() { | ||
this.getDeclaringType() instanceof TypeFreeMarkerTemplate and | ||
this.hasName("process") | ||
} | ||
} | ||
|
||
/** The `StringTemplateLoader` class of the FreeMarker Template Engine */ | ||
class TypeFreeMarkerStringLoader extends Class { | ||
TypeFreeMarkerStringLoader() { this.hasQualifiedName("freemarker.cache", "StringTemplateLoader") } | ||
} | ||
|
||
/** The `process` method of the FreeMarker Template Engine's `StringTemplateLoader` class */ | ||
class MethodFreeMarkerStringTemplateLoaderPutTemplate extends Method { | ||
MethodFreeMarkerStringTemplateLoaderPutTemplate() { | ||
this.getDeclaringType() instanceof TypeFreeMarkerStringLoader and | ||
this.hasName("putTemplate") | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
java/ql/src/experimental/semmle/code/java/frameworks/JinJava.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** Definitions related to the Jinjava Templating library. */ | ||
|
||
import java | ||
|
||
/** The `Jinjava` class of the Jinjava Templating Engine. */ | ||
class TypeJinjava extends Class { | ||
TypeJinjava() { this.hasQualifiedName("com.hubspot.jinjava", "Jinjava") } | ||
} | ||
|
||
/** The `render` method of the Jinjava Templating Engine. */ | ||
class MethodJinjavaRender extends Method { | ||
MethodJinjavaRender() { | ||
this.getDeclaringType() instanceof TypeJinjava and | ||
this.hasName("render") | ||
} | ||
} | ||
|
||
/** The `render` method of the Jinjava Templating Engine. */ | ||
class MethodJinjavaRenderForResult extends Method { | ||
MethodJinjavaRenderForResult() { | ||
this.getDeclaringType() instanceof TypeJinjava and | ||
this.hasName("renderForResult") | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
java/ql/src/experimental/semmle/code/java/frameworks/Pebble.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** Definitions related to the Pebble Templating library. */ | ||
|
||
import java | ||
|
||
/** The `PebbleEngine` class of the Pebble Templating Engine. */ | ||
class TypePebbleEngine extends Class { | ||
TypePebbleEngine() { this.hasQualifiedName("com.mitchellbosecke.pebble", "PebbleEngine") } | ||
} | ||
|
||
/** The `getTemplate` method of the Pebble Templating Engine. */ | ||
class MethodPebbleGetTemplate extends Method { | ||
MethodPebbleGetTemplate() { | ||
this.getDeclaringType() instanceof TypePebbleEngine and | ||
this.hasName(["getTemplate", "getLiteralTemplate"]) | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
java/ql/src/experimental/semmle/code/java/frameworks/Thymeleaf.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** Definitions related to the Thymeleaf Templating library. */ | ||
|
||
import java | ||
|
||
/** | ||
* A class implementing the `ITemplateEngine` interface of the Thymeleaf | ||
* Templating Engine such as the `TemplateEngine` class. | ||
*/ | ||
class TypeThymeleafTemplateEngine extends Class { | ||
TypeThymeleafTemplateEngine() { | ||
this.hasQualifiedName("org.thymeleaf", "TemplateEngine") | ||
or | ||
exists(Type t | this.getASupertype*().extendsOrImplements(t) | | ||
t.hasName("org.thymeleaf.ITemplateEngine") | ||
) | ||
} | ||
} | ||
|
||
/** The `process` or `processThrottled` method of the Thymeleaf Templating Engine. */ | ||
class MethodThymeleafProcess extends Method { | ||
MethodThymeleafProcess() { | ||
this.getDeclaringType() instanceof TypeThymeleafTemplateEngine and | ||
this.hasName(["process", "processThrottled"]) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.