Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple valid JWT Signatures for HMAC Algorithms (base64 malleability) #365

Open
rb-x opened this issue Dec 29, 2024 · 0 comments
Open

Comments

@rb-x
Copy link

rb-x commented Dec 29, 2024

When using HMAC algorithms (HS256, HS384, HS512), python-jose and PyJWT exhibit different signature validation behavior compared to Node's jsonwebtoken library. Multiple valid signature endings are accepted when modifying the last character.

TLDR: We have a base64 malleability which could be problematic for revocation that relies on the whole jwt instead of jti.

Here's a minimal reproduction script:

from jose import jwt as jose_jwt
import jwt as pyjwt
import string
import uuid
from datetime import datetime, timedelta

# jose 3.3.0
# pyjwt 2.10.1

def test_jwt_signatures(algorithm):
    payload = {
        "sub": str(uuid.uuid4()),
        "exp": int((datetime.now() + timedelta(hours=1)).timestamp()),
        "jti": str(uuid.uuid4())
    }
    secret = "test_secret_key"
    
    jose_token = jose_jwt.encode(payload, secret, algorithm=algorithm)
    pyjwt_token = pyjwt.encode(payload, secret, algorithm=algorithm)
    
    print(f"\n=== Testing {algorithm} ===")
    
    print("\nPython-JOSE:")
    print(f"JWT: {jose_token}")
    print(f"Original signature ends with: {jose_token[-1]}")
    
    valid_chars_jose = []
    base64_chars = string.ascii_letters + string.digits + '-_'
    
    for c in base64_chars:
        modified = jose_token[:-1] + c
        try:
            jose_jwt.decode(modified, secret, algorithms=[algorithm])
            valid_chars_jose.append(c)
        except jose_jwt.JWTError:
            pass
    
    if valid_chars_jose:
        print(f"Found {len(valid_chars_jose)} valid endings: {valid_chars_jose}")
    
    print("\nPyJWT:")
    print(f"JWT: {pyjwt_token}")
    print(f"Original signature ends with: {pyjwt_token[-1]}")
    
    valid_chars_pyjwt = []
    
    for c in base64_chars:
        modified = pyjwt_token[:-1] + c
        try:
            pyjwt.decode(modified, secret, algorithms=[algorithm])
            valid_chars_pyjwt.append(c)
        except pyjwt.InvalidSignatureError:
            pass
    
    if valid_chars_pyjwt:
        print(f"Found {len(valid_chars_pyjwt)} valid endings: {valid_chars_pyjwt}")

if __name__ == "__main__":
    for alg in ["HS256", "HS384", "HS512"]:
        test_jwt_signatures(alg)

image

NodeJS JWT

const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');

// jsonwebtoken:  9.0.2

function testJwtSignature(algorithm) {
    const payload = {
        sub: uuidv4(),
        exp: Math.floor(Date.now() / 1000) + 3600,
        jti: uuidv4()
    };
    const secret = 'test_secret_key';
    
    const token = jwt.sign(payload, secret, { algorithm });
    const origSig = token.slice(-1);
    
    console.log(`\n=== Testing ${algorithm} ===`);
    console.log(`JWT: ${token}`);
    console.log(`Original signature ends with: ${origSig}`);
    
    const validChars = [];
    const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
    
    for (const c of base64chars) {
        const modified = token.slice(0, -1) + c;
        try {
            jwt.verify(modified, secret, { algorithms: [algorithm] });
            validChars.push(c);
        } catch (err) {}
    }
    
    if (validChars.length > 0) {
        console.log(`Found ${validChars.length} valid endings: ${JSON.stringify(validChars)}`);
    }
}

const algorithms = ['HS256', 'HS384', 'HS512'];
algorithms.forEach(testJwtSignature);

image

@rb-x rb-x changed the title Multiple valid JWT Signatures for HMAC Algorithms Multiple valid JWT Signatures for HMAC Algorithms (base64 malleability) Dec 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant