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.

landing page

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.

/api/v1/get_ticket

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

403 Forbidden

After researching about bypassing forbidden endpoints, and checking other files. I’ve found out that this web application using HAProxy.

HAProxy is a free, very fast and reliable reverse-proxy offering high availability, load balancing, and proxying for TCP and HTTP-based applications. It is particularly suited for very high traffic web sites and powers a significant portion of the world's most visited ones. Over the years it has become the de-facto standard opensource load balancer, is now shipped with most mainstream Linux distributions, and is often deployed by default in cloud platforms. From 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.

The vulnerability, CVE-2021-40346, is an Integer Overflow vulnerability that makes it possible to conduct an HTTP Request Smuggling attack, giving it a CVSSv3 score of 8.6. This attack allows an adversary to “smuggle” HTTP requests to the backend server (HAProxy), without the proxy server being aware of it. From JFrog.

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.

guest token

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.

/api/v1/chat/{chatId}

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,

JWT token error

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.

CVE-2022-39227 - python-jwt is a module for generating and verifying JSON Web Tokens. Versions prior to 3.3.4 are subject to Authentication Bypass by Spoofing, resulting in identity spoofing, session hijacking or authentication bypass. An attacker who obtains a JWT can arbitrarily forge its contents without knowing the secret key. Depending on the application, this may for example enable the attacker to spoof other user's identities, hijack their sessions, or bypass authentication. From NIST NVD.

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.

edited JWT token

Paste the new token to view the flag.

/api/v1/flag

Resources

Last updated