..

Exploiting insecure Flask debugger console

Category: en

Introduction

With the popularity of Python in web server development, more and more people use frameworks like Flask. Flask is a web application framework, and it is not a web application server. Therefore, it needs a WSGI server to actually run the application.

Among those WSGI servers, Werkzeug is one of them. What is interesting about Werkzeug is that, it comes with a debugger. It is awesome during development, and with Debug=1 you can see what is going wrong with your web application.

However, when a developer forgets to disable debug mode in production, an attacker can make use of this to exploit the application.


Insecure Flask application

I will be using the following Flask application for the demo.

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def hello():
    return '<html><body><h2>My Notes</h2><li><a href="/notes?file=secret.txt"> secure notes </a></li></body></html>'

@app.route('/notes', methods=['GET'])
def notes():
    args = request.args
    filename = args["file"]
    with open(f'./notes/{filename}', 'r') as f:
        return f.read()
    return 'File not found'

Then I put a note under ./notes/secret.txt

Sup3rS3cr3t

Only 1 dependency which is Flask :) and you can run as follows.

FLASK_APP=main.py FLASK_DEBUG=1 flask run

It has a home page, that as a link to list of notes (1 for the demo), and /notes is GET request with query parameter for file name. However, there’s no input validation, and it is vulnerable to Local File Intrusion (LFI) vulnerability.

If you go to the homepage, you will see the following page.

image1

If you click secure notes or curl the URL directory, you will see notes content.

image2

However, since there is no input validation, LFI can be done as below.

image3

LFI alone is dangerous, but it cannot be code execution yet. So this is where /console comes it. By default, Werkzeug console is PIN protected.

image4

So no more code execution? NO

Because it can be cracked if we know some key parameters. We can know these information by first reading debug output. This can be done by crashing the application.

image5

image6

I read a non-existent file in order to crash the app and dump debug logs.


Exploiting the server

Using LFI vulnerability, I can read the debug logic code of werkzeug. This file also contains the code for generating the console PIN.

image7

In order to crack the Werkzeug console PIN, I need the following 6 parameters.

1. username

can get from enumeration or, in my case, from logs /home/khant/…

2. modname

flask.app

3. app class name

getattr(app, '__name__', getattr (app .__ class__, '__name__'))flask

4. flask app.py location

getattr(mod, '__file__', None) → from debug logs I can see where the package installed, so should be under /home/khant/Documents/work/security/demo/flask-hello-world/env/lib/python3.10/site-packages/flask/app.py

image8

5. mac address

read /proc/net/arp from LFI to find devices then /sys/class/net/DEVICE_ID/address, and you will receive a mac address. Example 62:c8:fb:aa:85:39, and you need to convert the hexadecimal to decimal.

image9

image10

6. machine ID

Read /etc/machine-id or /proc/sys/kernel/random/boot_i


Once you got everything, you can use the code from Hacktricks to generate a PIN.

image11

In my case, the pin is 298-808-092.

Now, when I enter this, I can log in to the Python console and execute system commands as below.

image12

It means I have code execution access on the system with the rights of the user that is used to run the Flask application.


PS

In this article, I showed how I can use the LFI vulnerability to gather information for cracking /console password.

This is not always the case; sometimes you already have a foothold on the system, but you realize another user (possibly a privilege user) is running Flask server, therefore, you can use this attack to either escalate privileges or perform lateral movement to another user.