🚀 Executive Summary
TL;DR: The `pac4j-jwt` library was found to be vulnerable to an `alg:none` authentication bypass, allowing attackers to forge JWTs and gain unauthorized access by circumventing signature verification. The critical fix involves upgrading to `pac4j-jwt` version 5.7.1 or later, alongside temporary application-level checks or network-edge blocking strategies.
🎯 Key Takeaways
- The `alg:none` vulnerability exploits a JWT specification allowance, where a library might skip signature verification if the `alg` header is explicitly set to `none`, enabling attackers to craft valid-looking tokens.
- A quick, temporary fix involves an application-level hot patch to manually decode the JWT header and reject any token where `decoded_header.alg.toLowerCase() === ‘none’` before the vulnerable library processes it.
- The permanent solution is to update the `pac4j-jwt` dependency to version `5.7.1` or later, which correctly handles and rejects tokens attempting to use the `alg:none` algorithm for signature bypass.
- As a ‘nuclear option,’ organizations can implement WAF or API Gateway rules to block requests containing the Base64Url-encoded `alg:none` header (`Bearer eyJhbGciOiJub25l`) in the Authorization header, providing an infrastructure-level defense.
A deep dive into the `pac4j-jwt` authentication bypass vulnerability, explaining why it happens and providing three practical, real-world solutions for DevOps and developers to secure their systems.
That 2 AM PagerDuty Alert: A Tale of JWTs and Trusting Too Much
I remember it vividly. 2:17 AM. My phone starts screaming with a PagerDuty alert for our `auth-service-prod-blue` cluster. Grafana was a sea of red—5xx errors everywhere. The on-call junior, bless his heart, was scrambling, seeing a mix of successful requests and “Invalid Signature” errors. He thought our signing key had been rotated incorrectly. But the successful requests… those were the scary part. They shouldn’t have been happening. It turns out we were getting hit by an old, nasty JWT vulnerability that had resurfaced in a library we depended on: the `alg:none` bypass. Someone was literally walking through our front door by handing the bouncer an ID that said, “You don’t need to check this,” and the bouncer was just letting them in.
So, What’s Actually Going On? The Root Cause
This isn’t some complex, cryptographic masterpiece of a hack. It’s depressingly simple. The JSON Web Token (JWT) spec allows for an `alg` (algorithm) header that specifies how the token is signed. One of the possible values is, you guessed it, `none`. This was intended for situations where the token’s integrity is already verified by other means. The vulnerability, which the team at Codeant found in `pac4j-jwt`, happens when a library sees a token with `alg: ‘none’` and, in its attempt to be helpful, says, “Okay, no algorithm specified, so I won’t bother verifying the signature.”
An attacker can take any valid, expired JWT, decode the payload, change the data (e.g., set `isAdmin: true`), change the header to `alg: ‘none’`, and re-encode it. They don’t need your secret key. When they send this forged token to your server, the vulnerable library just shrugs and accepts the payload as gospel. It’s a complete authentication bypass.
Fixing the Hole: From Duct Tape to Fort Knox
Alright, you’re in the hot seat. The service is vulnerable. What do you do? Let’s walk through the options, from the “stop the bleeding now” fix to the long-term solution.
1. The Quick Fix: The Application-Level Hot Patch
This is the “It’s 3 AM and I need to stop the attack” solution. You can’t wait for a full CI/CD pipeline run. You need to patch the running code or add a middleware check immediately. The idea is to manually reject any token that dares to show up with `alg: ‘none’` before the JWT library even gets to see it.
It’s hacky, but it works. In your code, where you receive the Authorization header, you decode just the header part of the token and check the `alg` value. If it’s `none`, you throw an error and log the attempt.
// This is pseudo-code, adapt it for your language (Java, Go, Python, etc.)
function validateJwtRequest(request) {
const token = request.getHeader('Authorization').split(' ')[1];
if (!token) {
return reject("No token provided");
}
// Decode only the header part of the JWT (it's just Base64Url encoded)
const header_part = token.split('.')[0];
const decoded_header = JSON.parse(atob(header_part));
// THIS IS THE HOT PATCH
if (decoded_header.alg.toLowerCase() === 'none') {
console.warn("CRITICAL: 'alg:none' attack attempt detected from IP:", request.ip);
return reject("Invalid token algorithm");
}
// If we passed, let the real library handle the rest
return pac4j.validate(token);
}
Darian’s Take: Look, this feels dirty, and it is. You’re adding a temporary bandage. But when `prod-db-01` is getting hammered with malicious requests, you do what you have to do to protect the system. Document this change aggressively and create a high-priority ticket to remove it once the permanent fix is in place.
2. The Permanent Fix: Update Your Damn Dependencies
This is the real solution. The bug exists in a specific version of a library. The maintainers have patched it. Your job is to update to the secure version. This is non-negotiable and highlights the importance of dependency management and vulnerability scanning.
For the `pac4j-jwt` issue, the fix is to upgrade to version `5.7.1` or later. This should be a simple version bump in your build file.
For a Maven project (`pom.xml`):
<!-- FIND THIS IN YOUR DEPENDENCIES SECTION -->
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-jwt</artifactId>
<!--
VULNERABLE VERSION! E.g., <version>5.7.0</version>
-->
<!-- CHANGE IT TO THE PATCHED VERSION -->
<version>5.7.1</version>
</dependency>
After updating, run your integration and security tests through your CI pipeline (you have those, right?) and deploy. This permanently inoculates this service against this specific vulnerability.
3. The ‘Nuclear’ Option: Block it at the Edge
Sometimes you can’t patch an application immediately. Maybe it’s a legacy service nobody wants to touch, or the deployment process takes days. In that case, you can use your infrastructure to protect the application from itself. You can block these malicious requests at your WAF (Web Application Firewall) or API Gateway (like NGINX, AWS API Gateway, etc.).
The strategy is to inspect the `Authorization` header for the pattern that indicates an `alg:none` token. A JWT is three Base64Url-encoded parts separated by dots. The first part is the header. The Base64Url encoding for `{“alg”:”none”}` is `eyJhbGciOiJub25lIn0`. So you can create a rule that blocks any request where the Authorization header contains that specific string.
Here’s a simplified view of what a rule might look like:
| Component | Rule Logic |
| Target | HTTP Header: Authorization |
| Condition | String Contains |
| Value | Bearer eyJhbGciOiJub25l |
| Action | BLOCK / DENY with HTTP 403 |
Warning: This is a powerful but blunt tool. Be careful with your regex or string matching to avoid false positives. It’s called the “nuclear option” for a reason—it protects everything behind it, but a mistake can cause a major outage. Test it in a staging environment first!
At the end of the day, that 2 AM incident was a harsh reminder: security isn’t just about firewalls and complex access policies. It’s in the weeds, in the dependencies you pull in every day. Stay vigilant, scan your code, and for goodness sake, never trust a token that tells you not to trust it.
🤖 Frequently Asked Questions
âť“ What is the `alg:none` JWT vulnerability in `pac4j-jwt`?
The `alg:none` vulnerability in `pac4j-jwt` is an authentication bypass where the library fails to verify the signature of a JSON Web Token if its `alg` (algorithm) header is explicitly set to `none`. This allows attackers to forge tokens with arbitrary payloads without needing the secret key.
âť“ How do the different solutions for the `alg:none` vulnerability compare?
The application-level hot patch offers immediate, temporary protection by rejecting `alg:none` tokens in code. Upgrading `pac4j-jwt` to 5.7.1+ is the permanent, recommended fix. Blocking at the edge (WAF/API Gateway) provides a network-level defense, useful for legacy systems or rapid response, but requires careful implementation to avoid false positives.
âť“ What’s a common implementation pitfall when using the ‘nuclear option’ (WAF/API Gateway) for this vulnerability?
A common pitfall with the ‘nuclear option’ is implementing overly broad or incorrect string matching in WAF/API Gateway rules. This can lead to false positives, blocking legitimate user traffic and causing a major outage. Thorough testing in a staging environment is crucial before deploying such rules to production.
Leave a Reply