Flask
A Python library for routing and hosting a website
Last updated
A Python library for routing and hosting a website
Last updated
Inject the Jinja2 templating language for when the render_template_string()
function is used
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.
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:
In Flask, it is also possible to read strings from query parameters. The following does not use many special characters while allowing you to put any special characters that you need in a ?a=
query parameter for the request triggering render_template
or render_template_string
:
When these don't cut it, try this phenomenal tool built specifically to bypass Jinja2 template injection filters. Given a server, it automatically detects the filter remotely to try and bypass it. This combines many tricks to bypass all kinds of character/word filters:
You should read the documentation of the tool above (English translation) to understand its usage. One of its most useful features is shown in the examples when you can recreate the source code of the filter you are up against. Passing a function that returns True
for valid requests and False
for blocked ones, it can locally prepare a bypass for you to send in one shot:
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.
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.
The public bits are defined like so:
username
:
The user that started the program
modname
:
"flask.app" if running Flask, otherwise recreate the environment and log this value
getattr(app, "__name__", type(app).__name__)
:
"Flask" if app.run(debug=True)
is used, and "wsgi_app" if DebuggedApplication
called manually
getattr(mod, "__file__", None)
:
Absolute path to the flask/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.
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:
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 be 0x001B44113AB7
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):
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:
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.
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)
Tip: When put in a script it might need the --legacy
argument to get correct timestamps. This depends on the Flask version
Using a Python script you can automate this forging process to forge lots of values and find different responses. For example:
Every line shows a small terminal icon, that when pressed will prompt for a PIN that can unlock an interactive Python console on the server. If you can find the PIN, you can execute Python code on the server resulting in RCE.
You can also speed this up significantly using , as can crack and even automatically detect Flask Session Cookies.