Skip to main content
  1. My Blog Posts and Stories/

STF 2022 Web: Hyper Proto Secure Note

··1087 words·6 mins

Recently, I participated with 3 other players in the Stack The Flags 2022 CTF. This is a writeup for the Web challenge Hyper Proto Secure Note.

CHALLENGE NAME
Hyper Proto Secure Note 1

An evil organization has been plotting to pollute the nearby village with its smog engine. Our intelligence team tells us that they have found that the smog engine has an exposed RDP service, and they have the username but not the password. The password is stored in the secure note application that bob uses. Find a way to steal the password to turn off the smog machine.

Exploring the challenge #

The challenge provided the source code for the web application. The full source code will be available for download here

Login Page
Login Page

When we first visit the url, we are greeted with a login page. Without knowing what to do with the login page, we can try to register a new account.

Register Page
Register Page

By entering a username and password, we can register a new account. After signing up, we are redirected to the profile page where we can login into the page.

Profile Page
Profile Page

Within the profile page, we see that we can retrieve a private key of our page. This section looks a little suspicious, so we can click on it to see where it takes us.

Retrieve key redirect
Redirection to secret page

This is the part of the page where it seems really suspicious. In the url bar, we can see that <base_url>/fastapi/retrievekey?uid=3 is shown. Does that mean that if we change the uid parameter, we can retrieve the private key of other users?

After 3 seconds, the page redirects me to the page with a section for me to add notes. This is the 2nd part that seems a little suspicious to me. If there is a way to alert bob to visit the page, we can somehow get his cookies that way. (This is the continuation of this challenge after this challenge, you can see it here)

Looking into the source code #

This is where I started looking into the source code for the part I have seen in the previous section. First, I decide to visit the section where the redirection is done.

# ./src/flaskapp/app.py
import flask
...
@app.route("/fastapi/<apiname>", methods=["GET"], strict_slashes=False)
@login_required
def retrievekey(apiname=''):
    '''Obtain a secure note key from backend API. 
    Update: Remediated IDOR attack from recent PT report.'''
    if (request.args.get("uid")):
        uid = request.args.get("uid")

        if(not uid.isnumeric()):    # input validation - data type
            return "Invalid Data Type: only numeric uid is accepted."
            
        if(uid == str(current_user.user["id"]) or current_user.user["isadmin"]):    # Authorisation check to prevent IDOR.
            # Redirection to FastAPI backend
            ...
        else:
            # authorisation check - failed
            html = "Unauthorised: You cannot retrieve the key of another user!"
            html += "\n<!-- uid="+uid+" current_user.user['id']="+str(current_user.user["id"])+" -->"
            return html
    else:
        return "Missing uid parameter."

As seem from the above source code, we can see that this website is implemented in flask. If the UID check passes within the if statement, the website will redirect us to the FastAPI backend. If the UID check fails, the website will return an error message.

Let us also take a look at the FastAPI backend.

# ./src/flaskapp/faskapiapp/main.py

#!/usr/bin/env python3
from fastapi import FastAPI
from fastapiapp import db

# Just a simple backend service
app = FastAPI()

@app.get("/")
async def root():
    return {"message": "FastAPI 0.1.0"}

@app.get("/retrievekey")
async def hpp(uid: str):
    r = db.getkey(uid)
    if(r and r['key']):
        location = "/securenote/" + r['key']
        result = 'You will redirect to your secure URL within 3 seconds or click <a href="' + location + '">here</a>.<br>'
        html = """
        <html>
            <head><meta http-equiv="refresh" content="3;URL='""" +location+ """'" /></head><body>"""+ result +"""</body>
        </html>"""
    else:
        html = 'No secure URL.'
    return {"message": html}

The FastAPI backend just seems to Lookup the database for your UID and return the secret URL that you can visit for your private notes URL. If the UID is not found, it will return an error message.

In order to get the secret url of Bob we must be able to pass the first check of if(uid == str(current_user.user["id"]) or current_user.user["isadmin"]) from the Flask backend as well as make sure that the FastAPI backend receives the uid of the user we want to query.

Normally, there is no way for us to retrieve the URL directly. However, due to the different implementations of the FastAPI backend and the flask website, we can exploit their differences to get the UID of another user.

Before we dive into the solution let us see how each of the programs work.

Difference between Flask and FastAPI #

Let us demonstrate the differences between the Flask backend and the FastAPI backend using this example below.

In order to show the example let us add the lines to the source code before we run them in docker.

# ./src/flaskapp/fastapiapp/main.py

...
    html += f"\nUID in FastAPI: {uid}\n" # Add to line 24
...
# ./src/flaskapp/app.py
...
msg = j['message'] + f"Current UID in Flask: {uid}" # Line 156
...

After adding these lines, we can see the UIDs that are received from the Flask and the FastAPI backend.

Building the dockerfile with the provided link, we can now try passing UIDs into the URL and see the difference.

Normal Circumstances
Normal Circumstances

Under normal circumstances, we can see that the UID that is received by the FastAPI backend is the same as the UID that is received by the Flask backend.

Multiple UID args
Multiple UID args

However, if we tweak it a bit, we can see that the UID in Flask and the UID in FastAPI are different. We can see that Flask received the first UID that is passed into the URL while FastAPI received the last UID that is passed into the URL.

Exploiting the differences #

By using this, we can receive the secret note of bob and find the flag there.

As the UIDs are created in order and I am number 3, Bob should be number 2.

The final payload URL:

http://157.245.52.169:31179/fastapi/retrievekey?uid=3&uid=2

Bob’s Secret Note
Bob’s Secure Note

The website redirects us to Bob’s Secret page and we can retrieve the flag from there.

Flag:

STF22{iDOr_W1tH_ServerSide_HTTP_PaR@m3t3r_P0lluT10n_BypAss}

Conclusion #

Through this challenge I learnt that it is important to check the source code of the website and the backend to see if there are any differences in the way the website and the backend receives the data. This can be used to exploit the website and retrieve the data that we want.

In this case the differences between the Flask backend and the FastAPI backend allowed us to retrieve the secret note of Bob and find the flag there.