LockTalk: CVE-2021-40346 x CVE-2022-39227
Platform: Hack The Box
Category/Tags: Cyber Apocalypse 2024, Web, CVE, JWT
Difficulty: Medium
Introduction
In "The Ransomware Dystopia," LockTalk emerges as a beacon of resistance against the rampant chaos inflicted by ransomware groups. In a world plunged into turmoil by malicious cyber threats, LockTalk stands as a formidable force, dedicated to protecting society from the insidious grip of ransomware. Chosen participants, tasked with representing their districts, navigate a perilous landscape fraught with ethical quandaries and treacherous challenges orchestrated by LockTalk. Their journey intertwines with the organization's mission to neutralize ransomware threats and restore order to a fractured world. As players confront internal struggles and external adversaries, their decisions shape the fate of not only themselves but also their fellow citizens, driving them to unravel the mysteries surrounding LockTalk and choose between succumbing to despair or standing resilient against the encroaching darkness.
Information Gathering
A simple web application that generates a token, viewing chat messages and flag through API with authorization.

I download the zip file containing the source code of this web application.
The routes.py code under /app/api is very straightforward. It provides the /get_ticket
endpoint to generate JWT token for Authorization that has claims object wherein the role and user (name) takes place, PS256
algorithm for token signature and the datetime expiration of the token. /chat/<int:chat_id>
endpoint to view conversation, can be accessed both guest and administrator. Lastly, /flag
endpoint to view the flag, can only be accessed by administrator.
#/app/api/routes.py
from flask import jsonify, current_app
import python_jwt as jwt, datetime
import json
import os
from app.middleware.middleware import *
from . import api_blueprint
JSON_DIR = os.path.join(os.path.dirname(__file__), 'json')
@api_blueprint.route('/get_ticket', methods=['GET'])
def get_ticket():
claims = {
"role": "guest",
"user": "guest_user"
}
token = jwt.generate_jwt(claims, current_app.config.get('JWT_SECRET_KEY'), 'PS256', datetime.timedelta(minutes=60))
return jsonify({'ticket: ': token})
@api_blueprint.route('/chat/<int:chat_id>', methods=['GET'])
@authorize_roles(['guest', 'administrator'])
def chat(chat_id):
json_file_path = os.path.join(JSON_DIR, f"{chat_id}.json")
if os.path.exists(json_file_path):
with open(json_file_path, 'r') as f:
chat_data = json.load(f)
chat_id = chat_data.get('chat_id', None)
return jsonify({'chat_id': chat_id, 'messages': chat_data['messages']})
else:
return jsonify({'error': 'Chat not found'}), 404
@api_blueprint.route('/flag', methods=['GET'])
@authorize_roles(['administrator'])
def flag():
return jsonify({'message': current_app.config.get('FLAG')}), 200
The middleware.py code under /app/middleware is the middleware script for checking if the role is guest or administrator. It is also for verifying if the token signature is valid or not.
#/app/middleware/middleware.py
from flask import request, jsonify, current_app
from functools import wraps
import python_jwt as jwt
def authorize_roles(roles):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'message': 'JWT token is missing or invalid.'}), 401
try:
token = jwt.verify_jwt(token, current_app.config.get('JWT_SECRET_KEY'), ['PS256'])
user_role = token[1]['role']
if user_role not in roles:
return jsonify({'message': f'{user_role} user does not have the required authorization to access the resource.'}), 403
return func(*args, **kwargs)
except Exception as e:
return jsonify({'message': 'JWT token verification failed.', 'error': str(e)}), 401
return wrapper
return decorator
The config.py is the file where the flags hide and where the JWT_SECRET_KEY
lives. It indicates that the 2048-bit RSA will be the generated key pair, it means impossible to crack this key.
#/config.py
from jwcrypto import jwk
import os
class Config:
DEBUG = False
FLAG = "HTB{f4k3_fl4g_f0r_t35t1ng}"
JWT_SECRET_KEY = jwk.JWK.generate(kty='RSA', size=2048)
Vulnerability 1
I execute the first endpoint to generate JWT token, but the request is forbidden.

So, I tried it using BurpSuite to view the behavior. Again, the response is forbidden only, no other information yet.

After researching about bypassing forbidden endpoints, and checking other files. I’ve found out that this web application using HAProxy.
I stumbled upon this HAProxy config file. I’ve found out that the /api/v1/get_ticket
endpoint is denied when accessed by any users. I explored this HAProxy if there is any exploit out there.
global
daemon
maxconn 256
defaults/api/v1/get_ticket
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend haproxy
bind 0.0.0.0:1337
default_backend backend
http-request deny if { path_beg,url_dec -i /api/v1/get_ticket }
backend backend
balance roundrobin
server s1 0.0.0.0:5000 maxconn 32 check
Yeah, there is an exploit for this tool. It is CVE-2021-40346.
Exploitation 1
I tried the exploit for this vulnerability but it didn’t work. However, I tried to bypass the endpoint using —path-as-is
flag to access the exact path.
curl --path-as-is http://<MACHINE_IP>/./api/v1/get_ticket
Gotcha! this is the token for role guest.

Vulnerability 2
Now that I’ve got the token for role guest, I tried to execute the second endpoint /api/v1/chat/{chatId}
with chat id. I thought that this is vulnerable to Local File Inclusion attacks, but wrong.

I have also tried to edit the key-value pair of the JWT token on jwt.io and using jwt_tool, but no luck. It always shows,

I check again the files and the version of required packages, python_jwt needs 3.3.3. This is sketchy, so I decided to look for vulnerabilities of this package < 3.3.3.
uwsgi
Flask
requests
python_jwt==3.3.3
Again, the version 3.3.3 is vulnerable to CVE-2022-39227.
Exploitation 2
I found the exploit for this vulnerability to be able to spoof and change the role to administrator to access the /flag
endpoint. CVE-2022-39227 is the exploit for this vulnerability. So, I install it and supply the JWT token and change the role to administrator.
python3 cve_2022_39227.py -j <JWT_TOKEN> -i "role=administrator"
Copy the whole object under “New token”, this will serve as the new JWT token without breaking the signature.

Paste the new token to view the flag.

Resources
Last updated