Skip to content

fix(jwt-auth): reject malformed JWT signature instead of erroring#13518

Open
shreemaan-abhishek wants to merge 2 commits into
apache:masterfrom
shreemaan-abhishek:fix/jwt-auth-malformed-signature
Open

fix(jwt-auth): reject malformed JWT signature instead of erroring#13518
shreemaan-abhishek wants to merge 2 commits into
apache:masterfrom
shreemaan-abhishek:fix/jwt-auth-malformed-signature

Conversation

@shreemaan-abhishek

Copy link
Copy Markdown
Contributor

Description

jwt-auth's custom JWT parser (apisix/plugins/jwt-auth/parser.lua) replaced resty.jwt verification with per-algorithm verifiers. verify_signature looked up the verifier and passed the decoded signature without guarding either path:

function _M.verify_signature(self, key)
    return alg_verify[self.header.alg](self.raw_header .. "." ..
               self.raw_payload, base64_decode(self.signature), key)
end

The per-algorithm verifiers assert on signature length and key validity, and base64_decode returns nil for an invalid base64url signature. So a token with a wrong-length signature (assert(#signature == 64, "Signature must be 64 bytes.")) or a non-base64url signature (#nil -> "attempt to get length of a nil value") makes the verifier raise a Lua error. Since the caller invokes verify_signature without a guard, the error propagates and the request returns 500 instead of 401 for any route protected by jwt-auth. An unauthenticated client that knows a valid consumer key can repeatedly trigger these internal errors and error-log noise.

This makes verify_signature defensive: guard the algorithm lookup and the base64url decode, and run the verifier under pcall so a malformed token is cleanly rejected as an invalid signature instead of crashing the request.

Which issue(s) this PR fixes:

N/A (reported via a private security scan)

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change (no user-facing behavior or config change; internal robustness fix only)
  • I have verified that this change is backward compatible

verify_signature indexed the per-algorithm verifier and passed the
decoded signature without guarding either. A token carrying an
unsupported alg, a non-base64url signature, or a signature of the
wrong length made the verifier raise a Lua error (e.g. "Signature
must be 64 bytes." or a length-of-nil error), which propagated as a
500 response instead of a clean 401 for any route protected by
jwt-auth.

Guard the algorithm lookup and the base64url decode, and run the
verifier under pcall so a malformed token is rejected as an invalid
signature rather than crashing the request.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Jun 11, 2026
- localize tostring to satisfy the lj-releng global check (CI lint)
- keep the verifier's own (verified, err) return values through the
  pcall wrapper instead of dropping the secondary error detail
- merge the two malformed-signature cases into one self-contained test
  to remove the order dependency between them
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jun 11, 2026
@shreemaan-abhishek

Copy link
Copy Markdown
Contributor Author

Addressed the lint failure and review feedback in the follow-up commit:

  • Lint (tostring global): localized tostring alongside the other standard-library locals so the lj-releng global check passes.
  • Verifier return contract: the pcall wrapper now captures and forwards the verifier's own (verified, err) values instead of dropping the secondary error detail.
  • Test order dependency: merged the two malformed-signature cases into a single self-contained test that sets up its own consumer/route and exercises both the wrong-length and non-base64url signatures.

Verified locally: luacheck + lj-releng clean, and t/plugin/jwt-auth.t passes (and the merged case still fails as expected when the fix is reverted).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants