Axeploit
← Back to posts

The Signature is Not the Secret: Why Your JWT Implementation is Probably a Screen Door

By Pallavi M

Back in the day, before the world decided that every single piece of software needed to be a distributed microservice running on a cluster of machines nobody actually understands, we had sessions. It was simple. You logged in, the server put a little file in a folder called /tmp, gave you a cookie with a random string, and that was that. If the server saw the cookie again, it looked in the folder. If the file was there, you were who you said you were.

It worked. It was robust. It was, dare I say, elegant.

But then, we decided that "state" was the enemy. We wanted "stateless" systems. We wanted our servers to have the memory of a goldfish so they could scale to infinity. Enter the JSON Web Token (JWT). The idea is clever: instead of the server remembering who you are, it hands you a signed note that says who you are. You carry the note around. When you want something, you show the note. The server checks the signature, and if it's valid, it trusts the contents.

It sounds like a great way to save on RAM. In reality, it’s a great way to give yourself a massive security headache. Because as it turns out, verifying a signature is a lot harder than checking if a file exists in a folder.

At Axeploit, we spend a lot of time looking at how systems break. And let me tell you, JWT signing logic is a goldmine. Developers treat these libraries like black boxes. They plug them in, they work, and they move on to the next Jira ticket. But the "black box" is often full of sharp edges.

Here are the seven signing errors that are currently letting attackers forge tokens in your (yes, probably your) apps.

1. The "None" Algorithm: The Invisible Ink of the Digital Age

This is the classic. It’s the "Hello World" of JWT vulnerabilities. The JWT header has a field called alg. This tells the server which mathematical wizardry was used to sign the token (like HS256 or RS256).

The spec, in its infinite wisdom, included an algorithm called none.

Why? Probably for debugging. But in a production environment, accepting none is like a bouncer at a club accepting a business card that says "I am definitely 21" written in crayon. If an attacker changes the header to {"alg": "none"} and removes the signature entirely, many poorly configured libraries will say, "Oh, okay! No signature needed. Welcome in, Admin!"

The Axeploit Rule: If your library doesn't have a hardcoded whitelist of allowed algorithms that excludes none, you aren't actually using signatures.

2. The HS256 vs. RS256 Identity Crisis (Algorithm Confusion)

This one is subtle and devious. There are two main types of signing:

  1. Symmetric (HS256): One secret key signs and verifies.
  2. Asymmetric (RS256): A private key signs; a public key verifies.

In a normal setup, the client has the public key to verify things, and the server keeps the private key safe. But what happens if an attacker takes that public key (which is, you know, public), signs a fake token with it using the HS256 algorithm, and sends it to the server?

If the server-side code is written lazily—if it just grabs the key it has on file and uses whatever algorithm the token claims to use—it will use the public key as a symmetric secret. The math checks out. The server thinks it signed the token itself. You are now logged in as the CEO.

3. Key Injection via Header (JKU/JWK Attacks)

The JWT spec allows for headers like jku (JWK Set URL) or jwk (JSON Web Key). These are meant to tell the receiver, "Hey, if you don't know which key to use, go to this URL to download the right one."

This is like a stranger giving you a locked box and a note that says, "If you want the key, just call my friend Tony at this number."

If your server blindly follows the jku link provided by an attacker, it will download the attacker's public key and use it to "verify" the attacker's forged token. Surprise! Tony is a liar, and the box is full of exploits.

4. The Weak Secret: "Password123" is Not a Key

Symmetric signing (HS256) is only as good as the secret. If your secret is "super-secret-key" or "company-name-2024," an attacker can brute-force it in seconds using a tool like Hashcat.

Once they have the secret, they can sign whatever they want. They don't even need to hack your server; they just need to do some math on their own laptop. If you aren't using a cryptographically strong, random string of at least 32 bytes, you might as well not have a secret at all.

5. Forgetting to Verify the Signature Entirely

You would be shocked at how often this happens. A developer writes a function to "decode" the JWT to get the user ID, gets it working, and forgets that "decode" is not the same as "verify."

They see the data, it looks right, they move on. The signature is sitting right there, unread and unloved, while the attacker changes their user_role from intern to super_user.

6. Key ID (KID) Manipulation

The kid header is used to tell the server which key to use from a database or a file system. It's supposed to be a simple string like "key-v1".

But what if an attacker changes it to ../../../../dev/null? Or a SQL injection payload? Or a path to a file they uploaded to your server earlier? If your code takes that kid and plugs it directly into a file system call or a database query to retrieve the signing key, you’ve just turned a JWT check into a Remote Code Execution (RCE) or a Directory Traversal.

7. Trusting the "exp" Claim Before Verification

The exp (expiration) claim tells the server when the token dies. Some libraries check this before they check the signature.

If an attacker finds a way to trigger an error or a specific behavior based on an expired token, they can probe your system without ever needing a valid signature. Always, always verify the integrity (the signature) before you trust any data inside the payload, including the expiration date.

Why This Matters for Axeploit

At Axeploit, we believe that security shouldn't be a bolt-on feature. It has to be part of the fundamental architecture. When we talk about "Exploiting the Gap," we're talking about these tiny, overlooked implementation details the difference between what the spec says and what the developer actually typed.

JWTs are powerful, but they are not "secure by default." They are a toolkit. If you use a hammer to drive a screw, don't blame the hammer when the chair collapses.

The Fix:

  • Hardcode your algorithms. Never let the token decide how it's verified.
  • Validate your keys. Use a strictly defined set of keys and never fetch them from untrusted URLs.
  • Use strong secrets. If you can remember your secret, it's probably too weak.
  • Use a reputable library, but read the docs. Don't assume the defaults are safe for your specific use case.

Software is hard. Security is harder. Don't make it easier for the bad guys by leaving your signature logic to chance.

Integrate Axeploit into your workflow today!