Flask
A Python library for routing and hosting a website
# Related Pages
pagePythonJinja2 Server-side Template Injection (SSTI)
Inject the Jinja2 templating language for when the render_template_string()
function is used
1. Detect
2. Find subclasses to use for RCE
Then take the response and replace ,
with \n
in Visual Studio Code to easily see the line number of the index. The 'subprocess.Popen'
key is an easy way to execute commands, but more can also be exploitable.
3. Use subclass for RCE
Find a vulnerable subclass and replace index 42
with the index of it in the __subclasses__()
:
Alternatively, try this one-shot that works on Flask applications specifically:
Filter Bypass
Werkzeug - Debug Mode RCE (Console PIN)
Werkzeug is a very popular HTTP back-end for Python. Libraries like Flask use this in the back, and you might see "werkzeug" related response headers indicating this. It has a Debug Mode that will show some code context and stack traces when a server-side error occurs. These lines can expand to a few more lines to leak some source code, but the real power comes from the Console.
This PIN is generated deterministically, meaning it should be the same every time, but different per machine. It simply uses some files on the filesystem to generate this code, so if you have some way to read arbitrary files, you can recreate the PIN yourself.
Source Code
In the Traceback, you will likely see a path that contains flask/app.py
. This is the path which the Flask source code is loaded from and will be needed later.
If you change the flask/app.py
to werkzeug/debug/__init__.py
, you will find the code that handles this Debug Mode and generates the PIN. There are a few different versions of this code as it has changed over the years, so to be sure of how it works you should read this file on the target.
The function of interest here is get_pin_and_cookie_name()
:
(note again that this code may be slightly different on the target)
The most important things to note are the probably_public_bits
and private_bits
, which are the inputs for the randomness.
Public bits
The public bits are defined like so:
username
: The user that started the programmodname
: "flask.app" if running Flask, otherwise recreate the environment and log this valuegetattr(app, "__name__", type(app).__name__)
: "Flask" ifapp.run(debug=True)
is used, and "wsgi_app" ifDebuggedApplication
called manuallygetattr(mod, "__file__", None)
: Absolute path to theflask/app.py
file that the Traceback shows. May in some cases also be.pyc
instead of.py
Most of these can be easily found by guessing or looking at the source code. Only the username might be unknown at first.
Finding the username
There are a few ways to make an educated guess about the username. The /proc/self/environ
file might contain a USER
variable, making it as simple as reading this file. If this does not work for any reason, try the method below:
In the /etc/passwd
file all users and their uid
s are listed:
This gives a list of possible names. For a webserver www-data
is common, but it could also be that another user on the system is hosting it.
To be sure, a trick you can use is to look at which users are using which port. By default, Flask uses port 5000, but this can be changed in the app.run()
code. This trick uses the /proc/net/tcp
file which shows a table of all the TCP connections on the system as a file:
Here are two columns of interest. The local_address
which is a hex-encoded IP and port number. Then uid
which is the number that corresponds with a username in /etc/passwd
. To decode the address and port, you can simply convert them from hex in a Python console:
Private bits
Lastly, there are two more private bits:
str(uuid.getnode())
: The MAC address of the target, in decimal format. For example:00:1B:44:11:3A:B7
would be0x001B44113AB7
in hex, and'117106096823'
in decimal. It can be found by reading the/proc/net/arp
file to find the interface in the Device column, and then request the/sys/class/net/[interface]/address
file to get the MAC address.get_machine_id()
: The way this machine-id is found again depends on the server werkzeug version, so read the function source in the same file to be sure. But often this is the/etc/machine-id
file, or if that does not exist, the/proc/sys/kernel/random/boot_id
file. After this value, a part of/proc/self/cgroup
is also added if it exists. Take the first line and this code on it (likely to be an empty string):Python
Generating the PIN
Finally, when you have all these required bits you can combine them in the same way the server would to recreate the PIN and access the console.
This should then generate the correct console PIN that you can put into the prompt when you try to execute Python code. After this is unlocked, you can simply run system commands:
Session Cookie
If you have a SECRET_KEY
of the Flask application, you can forge your own session=
cookies. This can be useful to bypass authentication or even try injection attacks inside the session's parameters.
Brute-Force
You can also speed this up significantly using Hashcat, as can crack and even automatically detect Flask Session Cookies.
Note that I have not always had successful results with hashcat. If you run into "No hash-mode matches the structure of the input hash" errors, try flask-unsign
or manually set up the HMAC signature for hashcat to crack (see Cracking Signatures for some similar examples)
Forging Session
Tip: When put in a script it might need the --legacy
argument to get correct timestamps. This depends on the Flask version
Scripted Forging
Using a Python script you can automate this forging process to forge lots of values and find different responses. For example:
Last updated