
إثبات المفهوم وتقرير فني لـ CVE-2026-65971 — حقن SQL عبر خاصية sortDirection في Livewire في power-components/livewire-powergrid (< 6.10.4)
sortDirectionإثبات المفهوم وكتابة فنية كاملة لـ CVE-2026-65971 / GHSA-7fgc-3h6c-698r،
ثغرة حقن SQL في power-components/livewire-powergrid
يمكن الوصول إليها عبر خاصية Livewire العامة sortDirection.
| CVE | CVE-2026-65971 |
| GHSA | GHSA-7fgc-3h6c-698r |
| الحزمة | power-components/livewire-powergrid (Composer / Packagist) |
| المتأثر | >= 6.0.0, < 6.10.4 |
| تم التصحيح | 6.10.4 |
| الضعف | CWE-89 — تحييد غير صحيح للعناصر الخاصة المستخدمة في أمر SQL |
| الخطورة | 7.6 عالية — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
| أبلغ من قبل | Caio Fabrício (@BiiTts) |
| الإفصاح | منسق، عبر استشارة أمنية خاصة على GitHub |
| ├── poc/exploit_powergrid_sqli.py working exploit — confirm + blind extraction | |
| ├── lab/ build the vulnerable app to reproduce it yourself | |
| ├── evidence/EVIDENCE.txt raw lab notes from confirmation | |
| ├── patch/security-fix-v6.10.4.diff the security-relevant portion of the official fix | |
| └── detection/ Sigma rules + Nuclei template for defenders |
---
## 🧠 ملخص
PowerGrid هو مكوّن datatable لإطار Laravel + Livewire (~2k نجمة، يُستخدم على نطاق واسع في
لوحات تحكم Laravel). حالة الفرز الخاصة به تعيش في **خاصيتين عامتين في Livewire**:```php
public string $sortField = 'id';
public string $sortDirection = 'asc';
في Livewire، تُعد الخاصية العامة جزءًا من تنسيق wire الخاص بالمكوّن — أي عميل يمكنه
الوصول إلى المكوّن يستطيع تعيينها عبر POST /livewire/update. هذا أمر مقصود بالتصميم؛ الحدود
الأمنية تكمن في ما يفعله الخادم بالقيمة.
ميزة naturalSort() في PowerGrid تُنشئ تعبير ORDER BY خامًا يحتوي على العنصر النائب الحرفي
{sortDirection}، ويستبدل خط أنابيب المعالجة هذا العنصر النائب بـقيمة الخاصية الخام،
غير المُتحقَّق منها قبل تمرير السلسلة إلى orderByRaw(). كلمة الاتجاه
تستقر بالتالي حرفيًا داخل SQL.
يرفض orderBy() الخاص بـ Laravel أي شيء ليس asc/desc، وهذا التحقق هو ما
يجعل مسار الفرز العادي آمنًا. الخلل هو وجود مسارٍ ثانٍ غير مُتحقَّق منه إلى نفس
الجملة — ويستطيع المهاجم الوصول إليه متجاوزًا المسار المُتحقَّق منه بالكامل
(انظر الالتفاف).
النتيجة: SQL عشوائي في جملة ORDER BY، قابل للاستغلال كأوراكل أعمى زمني/منطقي
لقراءة أي بيانات يمكن لمستخدم قاعدة البيانات قراءتها.
يمكن لأي شخص يمكنه الوصول إلى جدول PowerGrid يستخدم naturalSort قراءة بيانات عشوائية من
قاعدة البيانات — جداول أخرى، تجزئات كلمات مرور، رموز جلسات، مفاتيح API، سجلات عبر المستأجرين —
باستخدام أوراكل زمني/منطقي.
SELECT عليه.; UPDATE ... لا يتم تنفيذه. أثر الكتابة محدود بما يمكن أن تُحدثه استعلامات فرعية.SLEEP() واستعلامات فرعية ثقيلة —
يمكن إساءة استخدامها بسهولة لإشغال خيوط قاعدة البيانات.الامتيازات المطلوبة هي PR:L لأن جدول البيانات يوضع عادةً خلف مصادقة
التطبيق. إذا كان الجدول المتأثر يُعرض على صفحة غير مصادق عليها، فأعد الحساب مع
PR:N → 8.2 عالية.
ثلاثة ملفات، ثلاث مراحل. جميع الإشارات إلى الإصدار الضعيف v6.10.3.
`src/Concerns/Sorting.php````php public string $sortField = 'id'; // line 11 public string $sortDirection = 'asc'; // line 13
لا تحتوي أيٌّ من الخاصيتين على قائمة سماح، أو قاعدة تحقّق، أو مُعيِّن تطبيع. `sortDirection` يتم تعيينه أو قلبه فقط:```php
public function reverseSort(): string // line 37
{
return $this->sortDirection === 'asc' ? 'desc' : 'asc';
}
updatedSortDirection() (السطر 103) موجودة — وهي المكان الطبيعي للتحقق — لكن في v6.10.3 تتعامل فقط مع مسك دفاتر التحميل الكسول. ولا تفحص القيمة أبدًا.
نظرًا لأن Livewire يملا الخصائص العامة مباشرة من الطلب، فإن sortDirection يخضع بالكامل لسيطرة المهاجم، كسلسلة نصية عشوائية، في هذه المرحلة.
naturalSort() تزرع عنصرًا نائبًاsrc/Providers/Macros.php، الأسطر 102–116 — ماكرو العمود naturalSort:```php
Column::macro('naturalSort', function (bool $when = false, ?string $tableName = null): Column {
$this->enableSort();
if ($when) {
$this->rawQueries[] = [
'method' => 'orderByRaw', // <-- raw sink
'sql' => Sql::sortStringAsNumber($this->dataField),
'bindings' => [],
];
}
return $this;
});
`Sql::sortStringAsNumber()` يؤول إلى تعبير خاص بكل مُحرّك يُبنى بواسطة
`getSortSqlByDriver()` في `src/DataSource/Support/Sql.php` (الأسطر 60–100). كل نسخة من نسخ المُحرّك
تنتهي بنفس العنصر النائب الحرفي:```php
$default = "$sortField+0 {sortDirection}"; // line 76
'8.0.4' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER) {sortDirection}", // MySQL, line 81
'0' => "CAST($sortField AS INTEGER) {sortDirection}", // SQLite, line 84
'0' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS INTEGER) {sortDirection}", // PgSQL, line 87
'0' => "CAST(SUBSTRING(...) AS INT) {sortDirection}", // SQL Server, line 90
الثغرة مستقلة عن برنامج التشغيل — كل فرع يضمّن {sortDirection}.
`src/DataSource/Processors/Database/Pipelines/ColumnRawQueries.php````php private function resolvePlaceholders(?string $sql): ?string // line 56 { if (is_null($sql)) { return null; }
return preg_replace_callback('/\{(\w+)\}/', function ($matches) {
$property = trim($matches[1]);
return data_get($this->component, $property, ''); // line 65 — raw property, no escaping
}, $sql);
}
والتنفيذ، السطر 52:```php
$query->{$method}($resolvedSql, $resolvedBindings); // $method === 'orderByRaw'
data_get($this->component, 'sortDirection') ترجع سلسلة المهاجم، وpreg_replace_callback تدمجها في نص SQL، وorderByRaw() — التي بموجب العقد لا تهرب وسيطتها — تمررها إلى قاعدة البيانات.
لاحظ السخرية اللاذعة في السطر التالي: resolveBindings() (السطر 69) موجودة، وnaturalSort يعلن 'bindings' => []. آلية البارامترة الآمنة موجودة هناك تمامًا. لا يمكن استخدامها لكلمة اتجاه — ORDER BY x ? ليست SQL صالحة، والاتجاه لا يمكن أبدًا أن يكون بارامترًا مربوطًا — وهذا هو بالضبط سبب أن كلمة الاتجاه يجب أن تكون مدرجة في القائمة البيضاء بدلاً من ذلك.
POST /livewire/update ──▶ public string $sortDirection (Sorting.php:13, no validation) ──▶ data_get($component, 'sortDirection') (ColumnRawQueries.php:65) ──▶ "CAST(...) {sortDirection}" → "CAST(...) asc, (SELECT SLEEP(3))" ──▶ orderByRaw($sql) (ColumnRawQueries.php:52) ──▶ MySQL/MariaDB/PgSQL/SQLite/MSSQL
---
## 🔓 التحايل — لماذا لا ينقذك تحقق Laravel
هذا هو الجزء الذي يحوّل "الاستيفاء الخام" إلى ثغرة قابلة للاستغلال فعلاً، وهو السبب في بقاء المشكلة في حزمة ناضجة واسعة الاستخدام.
يعالج PowerGrid استعلامًا عبر **خط أنابيب**. هناك مرحلتان من ذلك الخط تمسّان اتجاه الفرز: