This week I spent some time learning about JSON Web Tokens (JWTs).  I had read about them in the past, but never really taken the time to dive in and really learn about them.

Check it, RFC7519 covers JWTs.

Let's take a quick look at what they are, and how we can bust them up. Eh?

Taking a look at the abstract section of the RFC:

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.  The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

So, yeah, it's just a way to transfer data and digitally sign it. It's transferrred in a base64URL encoded JSON format. It looks something like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiVGFjbyBEdWRlIiwiaWF0IjoxNjM2MTY5MDc0fQ.uFGbWPpRgcvc9THHkJsvDxoR0yjvTgps8-jF6A3vBaE

Grabbed that from here, btw: https://jwt.io

Notice how it's three chunks of base64URL-encoded text broken up by two dots? Awesome. Let's see what it looks like when we break that sucker up and decode it.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Becomes:

{
  "alg": "HS256",
  "typ": "JWT"
}

This is a pretty standard looking header. It's specifies the signing algorithm, which in this case is HS256, which means it's HMAC with SHA-256. It's a symmetric algorithm, which means there is only one key, or secret, used to sign and validate. The typ field just specifies the type of token, which in this case is JWT (there are also JSON Web Signatures [JWS], but we don't need to go into those today.)

Then we have a dot . separator.

Next, we have the payload.

Payload

This is the meat of our request. Let's take a look at this payload:

{
  "sub": "123456",
  "name": "Taco Dude",
  "iat": 1636169074
}

It's really just the JSON payload. In this case it's a sub, a name, "Taco Dude" and an iat, which is just a Unix time stamp that translates into Sat Nov 06 2021 03:24:34 GMT+0000.

This information can be read by anyone since it's just encoded text, so you don't want to include sensitive data in these tokens.

Signature

The signature is, obviously, used for signing. It ensures integrity of the message, making sure that it wasn't changed along the way. If using a private key, it's also designed to validate that the signer is who they say they are (or at least that whoever signed it has that private key ;)  ) JWT.io shows us in a bit more detail, where it takes the base64URL-encoded payloads and creates a secret.

Here's how it's created when using HMAC SHA256:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

What we get after all that is the JWT we're used to seeing:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiVGFjbyBEdWRlIiwiaWF0IjoxNjM2MTY5MDc0fQ.uFGbWPpRgcvc9THHkJsvDxoR0yjvTgps8-jF6A3vBaE

This little tool here lets ya screw with a JWT: https://jwt.io/#debugger-io

How can we attack JWTs?

Crack the secret

The first, and most obvious thing is that key. That HS256 algorithm uses a symmetric key for signing. This means, if you have a weak key, anyone with a valid JWT can crack it.

This cool little tool right here can help with that: https://github.com/ticarpi/jwt_tool

If you can find the key/secret, you can sign a request.

None Algorithm

Sometimes it's possible to use the "none" algorithm setting, which is kind of like saying, "There is no signature, so don't worry about it. We good!"

If things aren't configured totally correctly, the instance may accept this non-signed JWT, and you've got a win.

A few other things

There are a few other things you might find, like creds or some sort of sensitive data in the JWT, but that takes some effort to screw up. Doesn't mean it's impossible, just means whoever set things up probably doesn't understand the difference between encoding and encryption.

Additional information: