prototype pollution for endless jwt tokens

my favorite way to exploit server side prototype pollution vulnerabilities, besides the obvious low-hanging fruits like setting the isAadmin property to true, is to attack other libraries used by the vulnerable application.

it is nothing new and there exist many cool gadgets which can escalate a prototype pollution vulnerability into remote code execution.

for example, in this article you can read how setting one property of the object can lead to executing arbitrary javascript code when using ejs.

in this article i would like to present gadgets for the jsonwebtoken library which i discovered while i exploited a totally independent vulnerability during my recent engagement.

these allow for manipulating the signing and verifying process of the jwt tokens

sign me up

the general process of hunting for gadgets is to see what options do the libraries expose. usually, unless explicitly set, these are undefined and can be overwritten with prototype pollution.

the above options is generally all we can manipulate using prototype pollution. as expected, there it not much we can do here, since the most interesting options like expiresIn is almost always explicitly defined by the developers.

some less commonly used jwt-specific options like issuer, subject or jwtid are likely to be left undefined. or developers could set these values in the jwt payload itself, instead of providing the options

by polluting these values we can change some contents of the jwt token. since we manipulate the signing function, the token’s signature is still obviously correct.

verification

more interesting things can happen when we manipulate the token verification process.

generally, the worst possible scenario of a successful manipulation is a total bypass of the process which would lead to the backend accepting any jwt token.

i didn’t find a gadget which would allow this, but i found some which allow for expired tokens to still be accepted by the backend.

taking the time

by looking at the options, which the verify functionality uses, we can see the clock options.

the source code reveals that clockTimestamp can be used to overwrite the current time used for jwt verification. i am not sure what an usecase of this parameter would be, but it’s there.

using prototype pollution, we can change this value to a timestamp within our jwt token’s validity time interval to make it valid again. while it is an option, it could disrupt the functionality of the application by possibly invalidating other jwt tokens. there are better options to achieve our goal

clock tolerance

another option is to tackle the clockTolerance value. by setting it to a high value like 999…999 we can bypass the above check and make every jwt token essentially valid forever.

ignore all previous options

the same effect can be achieved even easier by setting the ignoreExpiration option, which is used just a few lines back

the great thing about this option is that its inverse, ignoreNotBefore can also be set to true which makes all tokens from the past or future always valid.

example

to demonstrate this gadget i prepared a small application which validates a jwt token according to the library’s documentation and examples. the application also has a prototype pollution vulnerability.

we can place the gadgets in our vulnerable endpoint and then look at the validation function with the debugger

as we expect, the polluted prototype affects the options object and sets the provided values to true

our polluted value is used in the jwt expiration check and skips it resulting in the jwt token being treated as valid

there we can see a request with an expired token to a generic /api/me endpoint. the request is processed successfully despite the token being outdated.

conclusion

default options of the jsonwebtoken library can be manipulated using prototype pollution. as a result, we can set jwt-specific fields within the signing function or attack the jwt verification process to accept expired tokens.

here is a copy-paste ready prototype pollution payload for always-valid tokens:

{
  "__proto__":{ 
    "ignoreNotBefore":true,
    "ignoreExpiration":true
  }
}

and here is a payload to manipulate the contents of the tokens

{
  "__proto__":{ 
    "issuer":"prototype-pollution",
    "subject":"prototype-pollution",
    "jwtid":"prototype-pollution",
    "keyid":"prototype-pollution"
  }
}