When trying to solve the Forged Signed JWT challenge, Juice Shop responds with both an UnauthorizedError: invalid signature and marks the challenge as solved. I do not know if my approach is wrong (and the challenge should not be marked as solved), or if there is a bug showing an error where it shouldn't.
Unknown, I have not tested this with older releases.
/api/Users using the forged JWT:curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6InJzYV9sb3JkQGp1aWNlLXNoLm9wIiwicGFzc3dvcmQiOiIwMTkyMDIzYTdiYmQ3MzI1MDUxNmYwNjlkZjE4YjUwMCIsInJvbGUiOiJhZG1pbiIsImxhc3RMb2dpbklwIjoiMC4wLjAuMCIsInByb2ZpbGVJbWFnZSI6ImRlZmF1bHQuc3ZnIiwidG90cFNlY3JldCI6IiIsImlzQWN0aXZlIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDIwLTAyLTA2IDA4OjI2OjE1LjY1OCArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDIwLTAyLTA2IDA4OjI2OjE1LjY1OCArMDA6MDAiLCJkZWxldGVkQXQiOm51bGx9LCJpYXQiOjE1ODA5Nzc2ODcsImV4cCI6MTU4MDk5NTY4N30.D_G5GDBrDpcKn3kZYg-CgBjilAVdqs0INcurxZ1sH9A" http://localhost:3000/api/Users
> [email protected] start C:\Users\Public\dev\juice-shop_9.3.1
> node app
info: All dependencies in ./package.json are satisfied (OK)
info: Detected Node.js version v12.14.1 (OK)
info: Detected OS win32 (OK)
info: Detected CPU x64 (OK)
info: Required file index.html is present (OK)
info: Required file styles.css is present (OK)
info: Required file main-es2015.js is present (OK)
info: Required file tutorial-es2015.js is present (OK)
info: Required file polyfills-es2015.js is present (OK)
info: Required file runtime-es2015.js is present (OK)
info: Required file vendor-es2015.js is present (OK)
info: Required file main-es5.js is present (OK)
info: Required file tutorial-es5.js is present (OK)
info: Required file polyfills-es5.js is present (OK)
info: Required file runtime-es5.js is present (OK)
info: Required file vendor-es5.js is present (OK)
info: Configuration default validated (OK)
info: Port 3000 is available (OK)
info: Server listening on port 3000
info: Solved challenge Score Board (Find the carefully hidden 'Score Board' page.)
info: Solved challenge Password Strength (Log in with the administrator's user credentials without previously changing them or applying SQL Injection.)
info: Solved challenge Login Admin (Log in with the administrator's user account.)
UnauthorizedError: invalid signature
info: Solved challenge Forged Signed JWT (Forge an almost properly RSA-signed JWT token that impersonates the (non-existing) user [email protected].)
info: Solved challenge Error Handling (Provoke an error that is neither very gracefully nor consistently handled.)
v12.14.1
6.13.4
I have not identified any relevant additional factors yet.
Thanks a lot for opening your first issue with us! ๐งก We'll get back to you shortly! โณ If it was a _Support Request_, please consider asking on the community chat next time! ๐ฌ
Here's how the challenge is verified today by having every request checked via app.use(verify.jwtChallenges()):
exports.jwtChallenges = () => (req, res, next) => {
if (utils.notSolved(challenges.jwtUnsignedChallenge)) {
jwtChallenge(challenges.jwtUnsignedChallenge, req, 'none', /jwtn3d@/)
}
if (utils.notSolved(challenges.jwtForgedChallenge)) {
jwtChallenge(challenges.jwtForgedChallenge, req, 'HS256', /rsa_lord@/)
}
next()
}
function jwtChallenge (challenge, req, algorithm, email) {
const decoded = jwt.decode(utils.jwtFrom(req), { complete: true, json: true })
if (hasAlgorithm(decoded, algorithm) && hasEmail(decoded, email)) {
utils.solve(challenge)
}
}
function hasAlgorithm (token, algorithm) {
return token && token.header && token.header.alg === algorithm
}
function hasEmail (token, email) {
return token && token.payload && token.payload.data && token.payload.data.email && token.payload.data.email.match(email)
}
I just reverted a "random fix attempt" where I only check for the challenges when no error occurs. Then the challenges were unsolvable. This needs some more intense debugging, b/c I have no clue where the signature error is actually raised. And also if that bug sneaked in months/years ago and just went unnoticed so far.
Did some regression testing with older versions to find out when the forged/unsigned JWT have stopped working as valid users
It didn't (at least not with the tokens from the e2e tests) since at least v7.5.0.
Is this really a bug? Since there really is no way to obtain the private keys for correctly signing the jwt, and changing algorithm to hs256 and passing an incorrect hmac would obviously throw an error.
Even the question prompt says "almost correctly signed signature", so isn't this expected behavior?
The way I understand this challenge, this is supposed to allow for a key confusion attack. If Juice Shop uses a vulnerable library and does not explicitly specify the algorithm to verify, it will use HMAC as specified in my JWT using the public RSA key to verify the signature. Since the public key can be obtained, it should be possible to create a JWT token that passes signature validation.
IMHO, solving this challenge should allow the attacker to access content using the forged token. So either I made a mistake and really didn't solve the challenge (then it should not be marked as such), or the forged token is not accepted as originally intended.
Exactly, both JWT challenges are supposed to give a valid token that actually "works", and I am pretty sure up to some point it did. The challenge verification was simply too weak, i.e. it checked before really checking if the token is valid. Thus, it was solvable for a long time without an actually working token. And I can't find out when this broke... :-(
Hi @ViktorLindstrm and @tghosth, would you mind taking a peek at this issue and PR #1310 as well b/c I you authored the challenges originally (#338, #392) and have a much deeper understanding of JWT magic than I do...? ๐
So my understanding is that this is now resolved except when the application server is Windows, correct?
Yes, that's it. I have no clue why, but it seems only Windows is unhackable in that regard... ๐
Most helpful comment
The way I understand this challenge, this is supposed to allow for a key confusion attack. If Juice Shop uses a vulnerable library and does not explicitly specify the algorithm to verify, it will use HMAC as specified in my JWT using the public RSA key to verify the signature. Since the public key can be obtained, it should be possible to create a JWT token that passes signature validation.
IMHO, solving this challenge should allow the attacker to access content using the forged token. So either I made a mistake and really didn't solve the challenge (then it should not be marked as such), or the forged token is not accepted as originally intended.