
CakePHP 5.4.2 - عالي - عن بُعد - SQLi - إزاحات النطاق/الإطار في WindowExpression مدمجة في SQL
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cakephp-window-sqli
تقرير: https://abraxaslabs.tech/research/cakephp-window-sqli
الفئة: SQLi الوصول: عن بُعد
CakePHP 5.4.2 (887b641) - Cake Software Foundation
أغلق CVE-2026-79752 دوال FunctionsBuilder cast / extract / dateAdd باستخدام ensureSimpleString(). لم تكن WindowExpression::range() وframe() ضمن ذلك الإصلاح. تأخذ rows() وgroups() قيمة ?int. لا تزال range() تأخذ سلسلة نصية. تربط buildOffsetSql الإزاحة ونوع الإطار وPRECEDING / FOLLOWING داخل OVER. لا يُستخدم الرابط (binder) إلا إذا كانت الإزاحة بالفعل من نوع ExpressionInterface.
التطبيق الذي يمرر بيانات الطلب إلى func()->count('*')->over()->range($start, $end) يُدخل سلسلة النص الخاصة بالمُستدعي داخل RANGE BETWEEN دون ربط ودون اقتباس.
| المعرّف | لا يوجد CVE بعد |
| الفئة | SQLi |
| الوصول | عن بُعد (تطبيق يمرر بيانات الطلب إلى range() / frame()) |
| CWE | CWE-89 |
| CVSS | عالٍ: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| المنتج | CakePHP (cakephp/database) |
| المتأثر | 5.4.2 (887b641de9f7aba06071f98f17edcbc573cbcf63) |
| المصادقة | تطبيق يمرر بيانات الاستعلام/النموذج إلى ->over()->range($start, $end) / frame() |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1 فقط |
المستخدم الذي يمكنه تعيين حد النافذة الذي يمرره التطبيق (تحليلات "آخر N أيام"، منشئ تقارير) يغيّر جملة OVER إلى SQL إضافي.
| الصلاحية | الطريقة / نقطة الوصول | النوع | مُثبت مختبريًا أو مُستنتج |
|---|---|---|---|
حقن جزء SQL داخل OVER (RANGE BETWEEN ...) | GET /?mode=range&start= إلى AggregateExpression::range($start, $end) / WindowExpression::frame() | سلامة SQL المُجمّع | مُثبت مختبريًا. SQL المُجمّع هو RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING مع رابط فارغ |
| قراءة أو تعديل الصفوف بصلاحية مستخدم قاعدة بيانات التطبيق | نفس الاستعلام، مُنفَّذ | سلامة / استطلاع قاعدة البيانات | مُستنتج من الحقن. هذا المختبر للتجميع فقط |
| تغيير نوع الإطار أو اتجاهه بالطريقة نفسها | frame($type, $offset, $direction, ...) يربط تلك السلاسل النصية أيضًا | سلامة SQL المُجمّع | مُستنتج من sql() / buildOffsetSql(). استخدم المختبر range() |
كسر rows() / groups() بالطريقة نفسها | مُقيّد بـ ?int | مُغلق | مُثبت مختبريًا أنه مُغلق. rows(3) هو ROWS BETWEEN 3 PRECEDING، غير مُقتبس وغير مربوط |
مُغلق على هذا الإصدار: FunctionsBuilder cast / extract / dateAdd / jsonValue (CVE-2026-79752 / CVE-2026-77635)؛ Query::limit / offset (مُقيّد بـ int). لا يستدعي cakephp/app الافتراضي range() ببيانات سلسلة الاستعلام. المتبقي هو واجهة برمجة النوافذ الموثقة. ليس RCE غير مُصادق عليه على تطبيق هيكلي. ليس صدفة (shell).
بقايا نفس المسألة: حقن SQL في collate، CRLF في المرفقات، إلغاء تسلسل ذاكرة التخزين المؤقت.
ثبّتُ إصدار cakephp/cakephp 5.4.2 بعد نشر GHSAs الخاصة بـ FunctionsBuilder SQLi لعام 2026. عشرة عمليات بحث على ذلك الوسم. عادت عمليات البحث الخاصة بـ CSRF / FormProtection، وView LFI، وBodyParser XXE، وmass assignment، وhelper XSS، وclientIp() بدون trustProxy فارغة في هذا الإصدار. لا يزال WindowExpressionTest::testRange يؤكد:
$w = (new WindowExpression())->range("'1 day'", "'10 days'");
buildOffsetSql لا يربط أبدًا إزاحة نصية:
protected function buildOffsetSql(
ValueBinder $binder,
ExpressionInterface|string|int|null $offset,
string $direction,
): string {
if ($offset === 0) {
return 'CURRENT ROW';
}
if ($offset instanceof ExpressionInterface) {
$offset = $offset->sql($binder);
}
return sprintf('%s %s', $offset ?? 'UNBOUNDED', $direction);
}
$binder غير مستخدم ما لم يكن الإزاحة بالفعل ExpressionInterface.
المنعطفات الخاطئة المسجّلة مسبقًا: التعامل مع rows() على أنها الثغرة (فهي من النوع int)؛ الادعاء بأن cakephp/app القياسي يكشف هذا في نموذج عام؛ تنفيذ SQL المحقون في SQLite كأداة تحقق (POC-READY هو تجميع فقط)؛ صدفة عكسية. مسرح.
python3 cakephp-window-sqli-Abraxas-Labs.py
أو
cd lab
./run.sh
استهدف فقط http://127.0.0.1:19440. تطبيق PHP صغير، cakephp/database 5.4.2، SQLite :memory:. يُمرَّر GET /?mode=range&start= كسلسلة نصية إلى range(). GET /?mode=rows&n=3 هو عنصر التحكم من نوع int.
SUCCESS cakephp-window-sqli pin=5.4.2 range-raw=yes rows-numeric=yes bound=no quoted=no CAKEPHP-WINDOW-SQLI-WITNESS
IOC عبارة عن SQL مُجمَّع:
SELECT (COUNT(*) OVER (RANGE BETWEEN 1) /* CAKEPHP-WINDOW-SQLI-WITNESS */ -- PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
التحكم:
SELECT (COUNT(*) OVER (ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)) AS n FROM sqlite_master
اربط الإزاحات الرقمية. اسمح بنوع الإطار (RANGE / ROWS / GROUPS) والاتجاه (PRECEDING / FOLLOWING) عبر قائمة السماح. بالنسبة لسلاسل الفواصل الزمنية، ضعها بين علامتي اقتباس أو اشترط ExpressionInterface. أضف اختبارًا يتحقق من أن إزاحة range() غير الصحيحة من نوع int تتحول إلى عنصر نائب (placeholder) أو نص حرفي بين علامتي اقتباس، وليس إلى SQL خام.
887b641de9f7aba06071f98f17edcbc573cbcf63)WindowExpression.php · AggregateExpression.phpGNU Affero GPL v3.0. راجع LICENSE. للاستخدام في مختبر loopback فقط. دون أي ضمان.