Post

LockTalk

LockTalk

LockTalk is a medium web challenge that was part of the HTB 2024 CyberApocalypse CTF.

Challenge hint: 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.

Video Walkthrough

Enumeration

Alt text

If we visit our IP address in a browser, we see a page for the LockTalk API. It appears to be some sort of API to help facilitate the communication between ransomware groups and their victims.

We see three available endpoints:

GET /api/v1/get_ticket This appears to generate a JWT (JSON Web Token), but it says the endpoint is locked. If we try and send a request to it, we get a 403 error.

GET /api/v1/chat/{chat_id} This endpoint says that it allows us to retrieves chat history by entering our JWT and a chat ID between 1 - 10. We need a valid JWT to be able to access this endpoint.

GET /api/v1/flag This endpoint retrieves the flag. It shows that it is locked and we also need a valid JWT to be able to access the endpoint.

Let’s start by seeing if we can find a way to generate a valid JWT.

If we look at app/api/routes.py we can see the code that will generate our JWT:

1
2
3
4
5
6
7
8
9
10
@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})

This will assign our role as guest and set our user to guest_user in our token. No matter how we try and access it though, we still get a 403 error.

403 Error

Fuzzing - 403 Bypass

HackTricks has a great post about 401 & 403 bypasses check it out here. One of the methods that caught my eye was path fuzzing, so let’s give it a try.

We’re going to run wfuzz against the /api/v1/get_ticket endpoint with this unicode wordlist, and we want to filter out 403 and 404 codes to make it easier to see a result.

Alt text

Success! By inserting the unicode character %2f%2f before api/v1/get_ticket we are able to successfully access the endpoint.

Now we can send our request again and we get a JWT!

Sending request via curl to get JWT

Now that we have a valid jwt, let’s see if we can access the second endpoint. By pasting our jwt into the box in our browser and entering a chat id, we are able to see the chat logs. There doesn’t seem to be anything super useful right off the bat, but maybe they’ll come in handy later.

CVE-2022-39227 - python_jwt 3.3.3

If we dig around a little more in the source code, we discover in conf/requirements.txt that this app is using python_jwt 3.3.3, which is vulnerable to CVE-2022-39227. There is a flaw in the JWT validation that allows attackers to re-use it’s signature with modified claims.

There is a great POC here that will allow us to change modify our token and hopefully give us access to the flag.

If we look back at the routes.py file, we see that the /api/v1/get_flag endpoint is checking to see if our role is administrator. Right now we are a guest, but maybe by using the POC, we can upgrade our token.

To use the script, we just need to pass it our jwt and tell it what we want the updated claims to be. In this case we want role=administrator.

Running the jwt exploit python script

Flag!

This gives us a new token, however by reading the README for the POC, the new token is a mix of JSON and compact representation. In order for it to work, we need to copy the entire value provided by the script.

1
{"  eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAyNzc1NDEsImlhdCI6MTcxMDI3Mzk0MSwianRpIjoibUZoR2JmRHpaamEyaklSREpqR3JIZyIsIm5iZiI6MTcxMDI3Mzk0MSwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiYWRtaW5pc3RyYXRvciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTAyNzc1NDEsImlhdCI6MTcxMDI3Mzk0MSwianRpIjoibUZoR2JmRHpaamEyaklSREpqR3JIZyIsIm5iZiI6MTcxMDI3Mzk0MSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ","signature":"dE3T8-ce3cG8_X0TlAoTAsQGLdR_uYbLPUV9yrJeDsd0yiN6C7eb2kfJRq_HUkVmC0JnAQUZ0B6oTnr9MruQ7mFoq2h99ihkYSN60P9JECEWpZ6Es4r2C4YjhUg-JAjyFVsUbI_sG5x2haGFP01Hj4aeF3n_Xn_aT41zYYsA8NfP16RRZfqCxrM6chmJo--MNefEuIVSXIlf-1r5DhPZ8gJCb9_LYXbpAF08Df7I4_kEH5OLRJgHLJyVMeIsmXJto8XLgSKQLf2LmWN7c1OiBJkbOPxbJqz2GfIjp_dBjjuxupZ4QXUDFm32FkJzDBSz5bFXDnTKPCSj6wutg4jfyA"}

If we paste our value into the box for the flag endpoint and click execute, we get our flag!

We have the flag!

This post is licensed under CC BY 4.0 by the author.