التحقق من رموز JWT
الرمز موقّع، فأين الخطر؟ في طريقة التحقق التي تثق بما يقوله المهاجم. ⏱ نحو 45 دقيقة.
- تتحقق من
JWTبالكامل: توقيع وexpوissوaud. - تشرح ثغرة
alg=noneوخلط الخوارزميات. - تثبّت الخوارزمية المتوقَّعة في التحقق.
- تختار مكان تخزين الرمز الأأمن.
١التحقق الكامل
JWT يحمي مطالباته بتوقيع، لكن الأمان في كيف تتحقق. التحقق الكامل أربع فحوص، لا التوقيع وحده:
| الفحص | يمنع |
|---|---|
| التوقيع صحيح | العبث بالمطالبات (ترقية الرتبة مثلًا) |
exp لم ينتهِ | إعادة استخدام رمز قديم |
iss متوقَّع | قبول رمز من مُصدِر غريب |
aud تطبيقك | قبول رمز صدر لتطبيق آخر |
const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const jwt = require('jsonwebtoken');const SECRET = 'my-secret'; app.get('/issue', (req, res) => res.json({ token: jwt.sign({ sub: 'u1', role: 'user' }, SECRET, { algorithm: 'HS256', expiresIn: '1h', issuer: 'rafee', audience: 'app', noTimestamp: true }) })); app.post('/check', (req, res) => { try { // تحقق كامل: الخوارزمية والمُصدِر والجمهور مثبّتة const claims = jwt.verify(req.body.token, SECRET, { algorithms: ['HS256'], issuer: 'rafee', audience: 'app' }); res.json({ sub: claims.sub, role: claims.role }); } catch (e) { res.status(401).json({ error: 'رمز غير صالح: ' + e.message }); }}); app.listen(3000);
الخادم يصدر رمزًا كامل المطالبات. والتحقق من رمز alg=none مزوّر (رتبة admin) يفشل 401؛ لأننا ثبّتنا الخوارزمية HS256 فلم نقبل none.
٢ثغرة alg=none وخلط الخوارزميات
أخطر ثغرتين في التحقق من JWT سببهما الثقة بما يقوله المهاجم في ترويسة الرمز:
| الثغرة | الحيلة | الدفاع |
|---|---|---|
alg=none | المهاجم يضع alg:none ويحذف التوقيع، فيقبله متحقق ساذج | ثبّت الخوارزميات المسموحة؛ لا تقبل none |
| خلط الخوارزميات | رمز موقّع بـ RS256 (مفتاح عام) يُرسل كأنه HS256 بالمفتاح العام سرًّا | ثبّت الخوارزمية المتوقَّعة فقط |
الجذر واحد: لا تدع الرمز يخبرك بخوارزمية التحقق. أنت تحدد الخوارزمية المتوقَّعة، لا ترويسة قد زوّرها المهاجم:
٣أين يُخزَّن الرمز؟
رمز الجلسة/الوصول في الواجهة له مخاطره حسب التخزين:
| المكان | الخطر | التخفيف |
|---|---|---|
| localStorage | يقرؤه أي XSS | تجنّبه للرموز الحسّاسة |
| كوكي httpOnly | لا يصله XSS | الأفضل، مع SameSite ضد CSRF |
وإن طالت صلاحية الرمز، فسرقته تدوم طويلًا. لذلك نقصر عمر رمز الوصول ونجدّده بـ رمز تجديد (refresh token) محفوظ بأمان.
توقّع وتحقّق
التحقق الكامل من JWT يشمل:
ثغرة alg=none سببها:
الدفاع عن خلط الخوارزميات:
أأمن مكان لرمز الجلسة في الواجهة:
الخلاصة
- التحقق الكامل: التوقيع صحيح، و
expلم ينتهِ، وissوaudكما تتوقّع. alg=noneوخلط الخوارزميات سببهما الثقة بخوارزمية يحددها الرمز.- ثبّت دائمًا الخوارزميات المسموحة في
verify، ولا تقبلnone. - خزّن الرمز في كوكي
httpOnlyلاlocalStorage، وأقصر عمره وجدّده.
تحدٍّ برمجي: ثبّت خوارزمية التحقق
متقدمنقطة التحقق تستدعي jwt.verify دون تثبيت الخوارزمية، فهي عرضة لثغرة alg=none. أصلحها بتثبيت HS256، فيُرفض الرمز المزوّر بلا توقيع.
- مرّر
{ algorithms: ['HS256'] }إلى verify. - الرمز الصحيح الصادر من /issue يُقبل.
- رمز
alg=noneالمزوّر (رتبة admin) يُرفض 401.
👀 اعرض الاستجابات المطلوبة
GET /issuePOST /check{"token": "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1MSIsInJvbGUiOiJhZG1pbiJ9."}
- الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
- الإصدار يعطي رمزًا
- رمز
alg=noneالمزوّر يُرفض ← 401 - يثبّت الخوارزميات
💡 تلميح 1
jwt.verify(token, SECRET, { algorithms: ['HS256'] }).💡 تلميح 2
alg=none بلا توقيع.💡 تلميح 3
alg:none وتوقيعه فارغ.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const jwt = require('jsonwebtoken');const SECRET = 'my-secret';app.get('/issue', (req, res) => res.json({ token: jwt.sign({ sub: 'u1', role: 'user' }, SECRET, { algorithm: 'HS256', noTimestamp: true }) }));app.post('/check', (req, res) => { try { const claims = jwt.verify(req.body.token, SECRET, { algorithms: ['HS256'] }); res.json({ sub: claims.sub, role: claims.role }); } catch (e) { res.status(401).json({ error: 'غير صالح' }); }});app.listen(3000);