jwt_tool icon indicating copy to clipboard operation
jwt_tool copied to clipboard

Fix castInput crash on list/dict JWT claim values

Open BreakfastSerial opened this issue 5 months ago • 0 comments

I'm facing an issue with a JWT with nested claims; Anonymized JWT:

{"sessionId":"1234","sessionState":"loggedIn","user":{"id":"[email protected]","email":"[email protected]","displayName":"Foo Bar","groups":[{"id":"4321","name":"BAZ"}]},"iat":1,"exp":2}

Original JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiIxMjM0Iiwic2Vzc2lvblN0YXRlIjoibG9nZ2VkSW4iLCJ1c2VyIjp7ImlkIjoiZm9vQGJhci5iYXoiLCJlbWFpbCI6ImZvb0BiYXIuYmF6IiwiZGlzcGxheU5hbWUiOiJGb28gQmFyIiwiZ3JvdXBzIjpbeyJpZCI6IjQzMjEiLCJuYW1lIjoiQkFaIn1dfSwiaWF0IjoxLCJleHAiOjJ9.  

This resulted in:

./jwt_tool.py -t https://example.com -rh "Cookie: jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiIxMjM0Iiwic2Vzc2lvblN0YXRlIjoibG9nZ2VkSW4iLCJ1c2VyIjp7ImlkIjoiZm9vQGJhci5iYXoiLCJlbWFpbCI6ImZvb0BiYXIuYmF6IiwiZGlzcGxheU5hbWUiOiJGb28gQmFyIiwiZ3JvdXBzIjpbeyJpZCI6IjQzMjEiLCJuYW1lIjoiQkFaIn1dfSwiaWF0IjoxLCJleHAiOjJ9." -M at

        \   \        \         \          \                    \ 
   \__   |   |  \     |\__    __| \__    __|                    |
         |   |   \    |      |          |       \         \     |
         |        \   |      |          |    __  \     __  \    |
  \      |      _     |      |          |   |     |   |     |   |
   |     |     / \    |      |          |   |     |   |     |   |
\        |    /   \   |      |          |\        |\        |   |
 \______/ \__/     \__|   \__|      \__| \______/  \______/ \__|
 Version 2.3.0                \______|             @ticarpi      

/home/kali/.jwt_tool/jwtconf.ini
Original JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiIxMjM0Iiwic2Vzc2lvblN0YXRlIjoibG9nZ2VkSW4iLCJ1c2VyIjp7ImlkIjoiZm9vQGJhci5iYXoiLCJlbWFpbCI6ImZvb0BiYXIuYmF6IiwiZGlzcGxheU5hbWUiOiJGb28gQmFyIiwiZ3JvdXBzIjpbeyJpZCI6IjQzMjEiLCJuYW1lIjoiQkFaIn1dfSwiaWF0IjoxLCJleHAiOjJ9.   
                                                                                                                                                           
=====================
Decoded Token Values:                                                                                                                                      
=====================                                                                                                                                      

Token header values:                                                                                                                                       
[+] alg = "HS256"
[+] typ = "JWT"

Token payload values:                                                                                                                                      
[+] sessionId = "1234"
[+] sessionState = "loggedIn"
[+] user = JSON object:
    [+] id = "[email protected]"
    [+] email = "[email protected]"
    [+] displayName = "Foo Bar"
Traceback (most recent call last):
  File "/home/kali/Python_VirtualEnvironments/jwt_tool/./jwt_tool.py", line 2152, in <module>
    rejigToken(headDict, paylDict, sig)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/Python_VirtualEnvironments/jwt_tool/./jwt_tool.py", line 1318, in rejigToken
    comparestamps, expiredtoken = dissectPayl(paylDict)
                                  ~~~~~~~~~~~^^^^^^^^^^
  File "/home/kali/Python_VirtualEnvironments/jwt_tool/./jwt_tool.py", line 1222, in dissectPayl
    if type(castInput(paylDict[claim][subclaim])) == str:
            ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/Python_VirtualEnvironments/jwt_tool/./jwt_tool.py", line 610, in castInput
    jsonInput = json.loads(newInput)
  File "/usr/lib/python3.13/json/__init__.py", line 339, in loads
    raise TypeError(f'the JSON object must be str, bytes or bytearray, '
                    f'not {s.__class__.__name__}')
TypeError: the JSON object must be str, bytes or bytearray, not list

jwt_tool crashes when decoding JWTs containing claims with list/dict values because castInput() always calls json.loads() and doesn’t handle non-string types, causing a TypeError: the JSON object must be str, bytes or bytearray, not list.

Sorry to do this, but it was the fastest/most direct approach for me to fix the issue; Buddy-boy ChatGPT proposed the following adjustment in the function castInput, which appears to be working for me.

def castInput(newInput):
    # short-circuit for already structured types
    if isinstance(newInput, (list, dict)):
        return newInput
    if isinstance(newInput, (bytes, bytearray)):
        newInput = newInput.decode('utf-8', errors='replace')

    if "{" in str(newInput):
        try:
            return json.loads(newInput)
        except ValueError:
            pass
    if "\"" in str(newInput):
        return str(newInput).strip("\"")
    elif newInput in ("True", "true"):
        return True
    elif newInput in ("False", "false"):
        return False
    elif newInput == "null":
        return None
    else:
        try:
            numInput = float(newInput)
            try:
                return int(newInput)
            except ValueError:
                return numInput
        except (ValueError, TypeError):
            return str(newInput)

BreakfastSerial avatar Aug 12 '25 09:08 BreakfastSerial