+
+
+ 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.
+
+
+
+ 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.
+
+
+
+
+ In the example given below, an untrusted HTTP parameter
+ code
+ is used as a Velocity template string. This can lead to remote code execution.
+
+
+
+
+ In the next example the problem is avoided by using a fixed template string
+ s
+ . Since, the template is not attacker controlled in this case, we prevent untrusted code execution.
+
+
+
+
+ Portswigger : [Server Side Template Injection](https://portswigger.net/web-security/server-side-template-injection)
+
+
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.ql
new file mode 100644
index 000000000000..18e47d2c6b35
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.ql
@@ -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."
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.qll b/java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.qll
new file mode 100644
index 000000000000..2f3113123aac
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/TemplateInjection.qll
@@ -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 {
+ 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