Juice-shop: [๐Ÿ›] Solving Forged Signed JWT results in UnauthorizedError: invalid signature

Created on 6 Feb 2020  ยท  10Comments  ยท  Source: bkimminich/juice-shop

:bug: Bug report

Description

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.

Is this a regression?

Unknown, I have not tested this with older releases.

:microscope: Minimal Reproduction

  • Log in as admin to receive a working JWT.
  • Copy the JWT to jwt.io and change the email to '[email protected]'.
  • Switch the algorithm to HS256.
  • Paste the the public RSA key as HMAC.
  • Send a GET request to /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

:fire: Exception or Error


> [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.)

:deciduous_tree: Your Environment


v12.14.1
6.13.4

Additional Information

I have not identified any relevant additional factors yet.

bug challenge critical help wanted

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.

All 10 comments

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

  • v8.5.0 :x:
  • v8.0.0 :x:
  • v7.5.0 :x:

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... ๐Ÿ˜‚

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cnotin picture cnotin  ยท  5Comments

bkimminich picture bkimminich  ยท  8Comments

bkimminich picture bkimminich  ยท  6Comments

sushi2k picture sushi2k  ยท  5Comments

agrawalarpit14 picture agrawalarpit14  ยท  4Comments