JSON Web Tokens (JWTs) have become a cornerstone of modern web application authentication. Their stateless nature and flexibility make them an attractive choice for developers. However, with great power comes great responsibility, and misconfigured JWT implementations can lead to severe security vulnerabilities.
In this post, we’ll explore common JWT vulnerabilities through a hands-on lab, demonstrating how a seemingly minor misconfiguration can lead to complete system compromise. We’ll build a vulnerable application, identify its security issues, and learn how to properly secure JWT implementations.
Before diving into vulnerabilities, let’s quickly review what JWTs are and how they work.
A JWT consists of three parts:
// Header { "alg": "HS256", "typ": "JWT" } // Payload { "user_id": 1, "username": "john_doe", "role": "user", "exp": 1234567890 }
The parts are Base64Url encoded and concatenated with periods:
header.payLoad.signature
JWTs are widely used because they are:
However, this flexibility also introduces potential security risks if not implemented correctly.
Let’s create a Flask application that demonstrates common JWT vulnerabilities. The application will have:
A weak secret key makes it easier for attackers to forge tokens. In a real-world scenario, using a strong, randomly generated key is essential.
app.config['SECRET_KEY'] = 'your-secret-key' # Vulnerable!
Without proper signature verification, an attacker can manipulate the token’s payload without detection.
def verify_token(token): try: # Vulnerable: No proper signature verification payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) return payload except jwt.InvalidTokenError: return None
Relying solely on the token for role verification can lead to unauthorized access if the token is compromised.
@app.route('/admin') def admin(): token = request.cookies.get('token') payload = verify_token(token) # Vulnerable: Only checks token role, not database if payload.get('role') == 'admin': return render_template('admin.html') return 'Access Denied', 403
The first vulnerability we’ll exploit is the weak secret key combined with insufficient token verification. Here’s how:
"user_id": 1, "username": "attacker", "role": "user", "exp": 1234567890 }
4. Modify the role claim:
"user_id": 1, "username": "attacker", "role": "admin", "exp": 1234567890 }
5. Re-sign the token using the known secret key
6. Replace the cookie in your browser Developer Tools
7. Attempt to access the admin panel
Another critical vulnerability is algorithm confusion. The application only accepts HS256, but doesn’t properly verify the algorithm:
"alg": "none", "typ": "JWT" }
2. Set the payload to:
"user_id": 1, "username": "attacker", "role": "admin", "exp": 1234567890 }
3. Remove the signature part (everything after the second period)
4. Use this token to access the admin panel
These vulnerabilities can have severe consequences in production environments:
Let's fix these vulnerabilities with proper security measures:
1. Strong Secret Key
# Generate a secure random key app.config['SECRET_KEY'] = os.urandom(32)
2. Proper Token Validation
def verify_token(token): try: payload = jwt.decode( token, app.config['SECRET_KEY'], algorithms=['HS256'], options={ 'verify_signature': True, 'require': ['exp', 'iat', 'user_id', 'role'] } ) return payload except jwt.InvalidTokenError: return None
3. Role Verification
@app.route('/admin') def admin(): token = request.cookies.get('token') if not token: return redirect(url_for('login')) payload = verify_token(token) if not payload: return redirect(url_for('login')) # Verify both token and database role user = User.query.get(payload.get('user_id')) if not user or user.role != 'admin': return 'Access Denied', 403 return render_template('admin.html')
JWT vulnerabilities can be subtle but devastating. By understanding these common issues and implementing proper security measures, we can build more secure applications. Remember:
The complete lab environment is available on GitHub for hands-on practice. Remember to only test these vulnerabilities in controlled environments and with proper authorization.
Disclaimer: This post is for educational purposes only. Always obtain proper authorization before testing security vulnerabilities in any system.