Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2026-25243 — إثبات مفهوم مستقر لـ CVE-2026-25243 (Redis RESTORE double-free -> تنفيذ تعليمات برمجية عن بُعد) | Kitploit
أدوات/GitHubGitHub/captain-woof/cve-2026-25243
تحليل الثغرات الأمنيةالاستغلالما بعد الاستغلالاختبار الاختراقالفريق الأحمرأمن قواعد البياناتاستغلال الملفات الثنائية
GitHubcaptain-woof/cve-2026-25243

CVE-2026-25243

إثبات مفهوم مستقر لـ CVE-2026-25243 (Redis RESTORE double-free -> تنفيذ تعليمات برمجية عن بُعد)

عرض المستودع
11منذ شهر واحدلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2026-25243 — ثغرة التحرير المزدوج في Redis RESTORE → تنفيذ تعليمات برمجية عن بُعد

تم التحقق على Rocky Linux 8.10، بمعمارية aarch64، مع Redis 8.6.2 وjemalloc 5.3.0.

المرجع: https://www.zeroday.cloud/blog/redis-cve-2026-25243-deep-dive

TLDR؛ استغلال مستقر، يعمل على مجموعة متنوعة من توزيعات أنظمة التشغيل والمعماريات.


الملخص التنفيذي

ما هذا؟ ثغرة تلف في الذاكرة في Redis تتيح لمهاجم مُصادَق تنفيذ أوامر عشوائية بصلاحيات مستخدم Redis. الهجوم حقيقي في العالم الفعلي ولا يتطلب سوى أمر RESTORE واحد — وهو عملية Redis عادية، وليست مقتصرة على المشرفين. يُثبت هذا الاستغلال تنفيذ تعليمات برمجية كاملًا عن بُعد (RCE) في أقل من ثانية واحدة.

التأثير؟ يمكن لأي عميل Redis مُصادَق تشغيلها، والضرر شامل: تنفيذ تعليمات برمجية عشوائية داخل عملية Redis (التي غالبًا ما تعمل بصلاحيات root في الحاويات). لا توجد طريقة للتخفيف من المخاطر دون تصحيح Redis نفسه.

كيف تعمل في لمحة؟ لدى Redis ميزة تسلسل (RESTORE) تأخذ كتلة من البيانات الثنائية وتعيد بناءها ككائن Redis. تختلف الشيفرة التي تتحقق من تنسيق الكتلة عن الشيفرة التي تلغي تسلسلها في كيفية تحليل تسلسلات معينة — وهو خلل يستغله المهاجم لإفساد الكومة (heap). وبمجرد إفساد الكومة، يكتسب المهاجم القدرة على قراءة وكتابة أي عنوان ذاكرة في عملية Redis، ومن هناك يختطف الحالة الداخلية للخادم لتنفيذ أمر قشرة (shell).

تقنية الاستغلال الحقيقية: هذه ليست مجرد انهيار بسيط. إنها : إفساد ← تداخل ← قراءة/كتابة عشوائية ← تسريب معلومات ← العثور على بنية الخادم ← اختطاف مؤشرات الدوال ← RCE. يعمل الاستغلال عبر 9 مراحل ويتطلب تسريب عناوين متعددة وقت التشغيل، وفحص بنى ثنائية، واكتشاف التزايُف (memory aliasing). ما يجعله يعمل عبر معماريات مختلفة (x86-64 وaarch64 وغيرها) أن جميع العناوين ، وليست مفترضة.

سلسلة استغلال للكومة (heap)
تُسرَّب من الهدف نفسه

1. الثغرة — بالتفصيل

CVE-2026-25243 هي زوج من ثغرات التحرير المزدوج (double-free) يمكن الوصول إليها من أمر RESTORE مُصادَق واحد. يلغي RESTORE key ttl <serialized-value> تسلسل كتلة RDB يتحكم بها المهاجم؛ تعيش كلتا الثغرتين في الفجوة بين المُتحقِّق (validator) الذي يفحص الكتلة والمحوِّل (converter) الذي يجسّدها.

الخلل 1 — تحويل zipmap القديم (CWE-415، المسار الذي يستخدمه هذا الاستغلال). يختلف مُتحقِّق zipmap (zipmapValidateIntegrity()) عن المحوِّل (zipmapNext()) حول ترميز طول زائد عن الحاجة. يمكن قانونيًا كتابة الطول الصغير 4 بالصيغة الطويلة المكونة من خمسة بايتات FE 04 00 00 00. يستهلك المُتحقِّق عددًا من البايتات، بينما يستهلك المحوِّل عددًا آخر — أي فقدان تزامن في التحليل بمقدار 4 بايتات. لذلك يمشي المحوِّل عبر بنية مختلفة عن تلك التي تم التحقق منها، فتفشل lpSafeToAdd() بعد أن أُدرج الحقل بالفعل في القاموس، ويحرر مسار التنظيف الحقل مرتين: مرة عبر dictRelease() ومرة أخرى عبر sdsfree().

الخلل 2 — تحميل PEL لمستهلك الدفق (CWE-415). في rdbLoadStreamConsumersGroup()، تؤدي PEL لمستهلك تحتوي على معرّف إدخال مكرر إلى فشل استدعاء raxTryInsert() الثاني، الذي يستدعي streamFreeNACK() على كائن streamNACK لا يزال مملوكًا لـ PEL العام للمجموعة. فيُحرَّر مرتين. (يمكن اختياره باستخدام --vuln-type stream.)

كل خلل من هذين يمنح المهاجم قطعة ذاكرة حرة ومُشارًا إليها في الوقت نفسه — نقطة البداية الكلاسيكية لاستغلال تداخل الكومة (heap-overlap).

التأثير: عميل Redis مُصادَق (بدون صلاحيات إدارية؛ RESTORE أمر بيانات عادي) يكتسب تنفيذ تعليمات برمجية عشوائية بصلاحيات مستخدم redis — أي root في صورة الحاوية الافتراضية.

2. كيف يعمل الاستغلال

تسع مراحل، كل واحدة منها تحوّل بدائية أضعف إلى أخرى أقوى:

المرحلةالبدائية المكتسبةالآلية
0ملف تعريف الهدفINFO server / INFO memory → الإصدار، المعمارية، التوزيعة، pid، مسار الملف التنفيذي، وقت البدء، المخصّص
1تحرير مزدوجأمر RESTORE لبيانات zipmap (أو stream) مشوّهة
2مفتاحان يتشاركان نفس الذاكرةرشّ مفاتيح علامة على القطعة المحرَّرة، وكشف التزايُف، ثم الكتابة فوق ترويسة SDS لأحد المفتاحين عبر توأمه لتضخيمها إلى نافذة "memview" بحجم 1 ميجابايت
3قراءة/كتابة عشوائيةالعثور على كائن INCRBYFLOAT داخل memview واختطاف حقل ptr الخاص به: GETRANGE/SETRANGE على ذلك المفتاح يقرآن/يكتبان الآن أي عنوان
4مؤشر الصورةفحص الكومة بالاتجاه الخلفي بحثًا عن قيمة داخل صورة redis-server
5&serverالنزول حتى ترويسة ELF، وفحص program headers، وتفريغ القطاع القابل للكتابة، ومطابقة server.pid
6حمولة في الذاكرةكتابة "/bin/sh", "-c", "<cmd>" مع مصفوفة argv داخل memview
7بنية مختطفةاستبدال server.executable و server.exec_argv و server.enable_debug_cmd
8RCEDEBUG CRASH-AND-RECOVER → restartServer() → execve(server.executable, server.exec_argv, environ)

كيفية التشغيل

root@kitploit:~
python3 exploit.py --host 127.0.0.1 --port 6379 \
    --password mypassword --cmd 'id > /tmp/pwned123.txt'

التحقق:

root@kitploit:~
cat /tmp/pwned123.txt
# uid=0(root) gid=0(root) groups=0(root)

3. سجل التغييرات

2026-08-06 — إعادة صياغة لقابلية النقل والموثوقية والسرعة

نقطة البداية: كان الاستغلال مقتصرًا على x86-64 وكان يفشل في المرحلة 3 على هدف aarch64. الحالة النهائية: RCE كامل على aarch64 / Rocky Linux 8.10 في أقل من ثانية واحدة، بـ 116 أمر Redis.

أ) البصمة الرقمية للهدف وقت التشغيل (جديد، المرحلة 0). لم يعد يُفترض أي شيء بشأن الهدف. يعطي INFO server + INFO memory إصدار Redis، ومعمارية وحدة المعالجة (من سطر os:)، وعائلة التوزيعة (مستنتجة من gcc_version)، والمخصّص (allocator)، والأهم من ذلك — ثلاث نقاط تحقق مرجعية: process_id و executable و stat_starttime بالضبط (server_time_usec/1e6 - uptime_in_seconds). تقارن المراحل اللاحقة قيمها بهذه النقاط بدلًا من التخمين.

ب) تخطيط ذاكرة مستقل عن المعمارية. استُبدلت الثوابت الأربعة المثبتة لمعمارية x86-64 (BINARY_ADDR_MIN/MAX، HEAP_ADDR_MIN/MAX) بجدول لكل معمارية (ARCH_PROFILES) يغطي x86_64 وaarch64 (بمساحتي العناوين الافتراضية 39 و48 بت) وriscv64 وppc64le وs390x، مع مواضع ET_EXEC وET_DYN لكل منها، بالإضافة إلى بديل عام واسع لأي شيء غير مُدرج. كان هذا هو السبب الفعلي لفشل الاستغلال على هذا الهدف: المؤشر المُسرَّب 0x0000ffff8a5fdf32 هو عنوان mmap سليم تمامًا لمعمارية aarch64 رفضه فحص النطاق الخاص بـ x86-64.

ج) التحقق من التسريب عبر الإجماع (المرحلة 3). بدلًا من الوثوق بنافذة كومة مثبتة، يجمع الفحص الآن كل كائن 1337.NNNNNN صالح بنيويًا داخل memview، ويتطلب أن يشتق اثنان منها على الأقل نفس عنوان قاعدة memview (ptr - offset_of_value). عمليًا، يتفق 502 مرشحًا، وهذا دليل لا يمكن لأي جدول نطاقات تقديمه. ثم يقوم المؤشر المؤكَّد بمعايرة نافذة الكومة وقت التشغيل. كما نُقل التحقق من التنسيق إلى ما قبل اختبار التحكم بالكتابة (المكلف في الجولات).

د) حد فحص المرحلة 3 (إصلاح خلل). كان الفحص يمتد إلى 10 ميجابايت مثبتة بينما حجم memview هو 1 ميجابايت، فكان يقرأ ما بعد النهاية، ويتلقى ردًا فارغًا ويتوقف مع رسالة AssertionError: Empty data from memview. أصبح الآن محدودًا بقيمة STRLEN الحقيقية لـ memview، ويقرأ 256 كيلوبايت لكل جولة بدلًا من 64 كيلوبايت، وأُزيلت حلقة إعادة المحاولة مع النوم غير المجدية 6×1 ثانية.

هـ) إعادة كتابة المرحلة 5: موجَّهة عبر ELF، خالية من الانهيار (الأهم). كان التنفيذ القديم يفحص للأمام من مؤشر صورة، مجربًا العناوين وقارئًا أي طول تدّعيه ترويسة SDS تالفة. على هذا الهدف، مشى خارج نهاية القطاع القابل للقراءة فقط مباشرة إلى الفجوة غير المعيّنة عند 0x715000 وقتل الخادم (SIGSEGV في getrangeCommand → memcpy). لا يمكن جعل الفحص الأعمى آمنًا. البديل حتمي:

  1. العثور على قاعدة الصورة. النزول صفحةً صفحة من أدنى مؤشر صورة مُسرَّب. الفحص بلا كلفة: أول خمسة بايتات من كل صورة ELF64 هي 7f 45 4c 46 02، وتأخذ sdslen() بايت الأعلام من ptr[-1] — لذا فإن توجيه الكائن المختطَف إلى base+5 يجعل e_ident[EI_CLASS]=0x02 هو بايت الأعلام، أي SDS_TYPE_16، الذي يكون طوله هو uint16 عند base+0 = 0x457f (0x7f45 بترتيب البايتات الكبير). قيمة STRLEN البالغة 17791 تمامًا هي توقيع ELF. لا حاجة إلى نسخة محلية من الملف الثنائي — تُقرأ الترويسة من ذاكرة الهدف نفسه.

  2. فحص program headers للحصول على الحدود الدقيقة وقت التشغيل لكل قطاع PT_LOAD (مع معالجة إزاحة التحميل ET_DYN للأهداف من نوع PIE). كل قراءة لاحقة تُقيَّد بتعيين فعلي للذاكرة، لذا أصبح الانهيار عند الفجوة غير المعيّنة مستحيلًا بنيويًا.

  3. تزوير ترويسة SDS واحدة في خانة مُصفَّرة ضمن القطاع القابل للكتابة، مما يجعل كامل .data/.bss قابلاً للقراءة في حفنة من الجولات بدلًا من مئات الآلاف من فحوصات البايتات. تُحفظ البايتات المستبدلة ثم تُستعاد.

  4. مطابقة server.pid مع pid الوارد من INFO — اختبار مساواة دقيق لثمانية بايتات — ثم التأكيد بإلغاء الإشارة إلى server.executable ومقارنة السلسلة النصية مع قيمة executable في INFO. كانت الشيفرة القديمة تقبل استدلالًا فضفاضًا قائمًا على شكل من سبعة حقول؛ أما الآن فيتم تحديد البنية تحديدًا قاطعًا.

و) تقوية المرحلة 4. يأخذ مُتحقِّق Lua قائمة النطاقات الكاملة لكل معمارية (بحيث يتم التعرف على صورة غير PIE عند 0x400000 وصورة PIE عند 0xaaaa… معًا) ويستبعد نافذة الكومة المعايَرة. ويعيد عدة مرشحين بدلًا من مرشح واحد، فالاختيار السيئ يكلف إعادة محاولة بدلًا من إفشال التشغيل.

ز) المرحلة 7 ذاتية التحقق. كان يتم تحديد موقع enable_debug_cmd عبر قيمة مثبتة stat_starttime - 0x3c. الآن تُعرف قيمة stat_starttime المتوقعة بدقة من INFO (نافذة زمنية من 3 ثوانٍ بدلًا من 30 يومًا)، ونمت نافذة قراءة البنية من 4 كيلوبايت إلى 32 كيلوبايت (stat_starttime يقع عند الإزاحة 0x9e0، أي أبعد بكثير من الحد القديم)، والأهم — يُتحقق من كل إزاحة مرشحة بأوراكل حي: اضبط البايت، وأرسل DEBUG SET-ACTIVE-EXPIRE 1، ولاحظ ما إذا كان الخادم يقبله. تُستعاد التخمينات الخاطئة قبل المحاولة التالية، لذا يُعثر على العلامة في أي بناء بدلًا من افتراضها. ما زال -0x3c يُجرَّب أولًا وتأكدت صحته لإصدار 8.6.2 (الإزاحة 0x9a4).

ح) الكتابات تصيب هدفها فعلًا (المرحلة 5). يستدعي setrangeCommand() الدالة dbUnshareStringValue()، التي تنسخ القيمة ما لم يكن encoding == RAW && refcount == 1. الآن يُصفَّر بايت الترميز قبل أول كتابة عبر المؤشر المختطَف، فتصل الكتابات إلى العنوان الهدف بدلًا من نسخة خاصة.

ط) تبسيط الحمولة. أُزيلت كل آليات الاتصال العكسي/القشرة العكسية، واللافتة النصية ASCII، و;sleep 5 الملحقة. الحمولة هي تمامًا /bin/sh -c '<--cmd>' ولا شيء غير ذلك. القيمة الافتراضية لـ --cmd هي id > /tmp/pwned123.txt.

ي) السرعة. تجمع المرحلة 4 ثلاثة مرشحين بدلًا من 8؛ وتستبدل المرحلة 5 نحو ~10^5 فحصًا للبايتات بنحو ~40 قراءة مجمّعة؛ وتستخدم المرحلة 3 قراءات بحجم 256 كيلوبايت وتتخطى الجولات بالنسبة إلى المرشحين الذين يفشلون في التحقق المحلي. السلسلة بأكملها: 116 أمرًا، وأقل من ثانية واحدة.

النتيجة: uid=0(root) gid=0(root) groups=0(root) في /tmp/pwned123.txt على حاوية الهدف.

ملاحظات قابلية النقل

  • المعمارية تُكتشف، ولا تُفترض. المرحلة 5 المعتمدة على ELF محايدة تجاه المعمارية بطبيعة بنائها (إذ تقرأ program headers الخاصة بالهدف) وتتعامل مع صور PIE وغير PIE، وبترتيب البايتات الصغير والكبير على حد سواء.
  • التوزيعة تُذكر لصالح المُشغِّل؛ إذ لا اعتماد وظيفي للاستغلال عليها. الافتراض الوحيد المتعلق بنظام الملفات هو /bin/sh، وهو ما تطلبه معايير POSIX وFHS.
  • الإصدار: يُختار إصدار RDB وحجم بنية stream استنادًا إلى redis_version (يدعم 7.x و8.x). تأتي إزاحات حقول البنية (executable=24، exec_argv=32) من ABI الخاص بـ LP64، ويُكتشف enable_debug_cmd ويُتحقق منه وقت التشغيل بدلًا من تثبيته في الشيفرة.
  • الأهداف ذات 32 بت مرفوضة صراحةً في المرحلة 0 (لأن الحمولة تبني مؤشرات 64 بت) بدلًا من الفشل بشكل غامض في مرحلة لاحقة.

2026-08-06 (لاحقًا) — تحصين الاستقرار بعد اختبارات التشغيل المتكرر

جاءت القياسات 13/13 عملية تشغيل ناجحة على مسار zipmap الافتراضي (5 + 8 على التوالي)، كل واحدة منها تكتمل في ≤1 ثانية. ظهرت ثلاث مشكلات فقط عند التنفيذ المتكرر، وقد أُصلحت الآن:

ك) حالة سباق SAVE في المرحلة 0. كان من الممكن أن يتوقف التشغيل مع رسالة ERR Background save already in progress عندما يكون الحفظ الخلفي لتشغيل سابق (أو لـ redis نفسه) ما زال قيد التنفيذ. الآن تُعاد محاولة SAVE لمدة تصل إلى 15 ثانية، وإذا تعذّر ذلك، يستمر التشغيل دون نقطة التحقق بدلًا من التوقف.

ل) إعادة الاتصال أثناء إعادة تشغيل الهدف (--connect-retries، الافتراضي 10). المحاولة الفاشلة تترك الكومة مُفسدة، لذا فإن أمر FLUSHALL في التشغيل التالي يحرر القطع المسمومة ويُسقط الخادم. يعاد تشغيله بعد ثوانٍ ويكون قابلًا للاستغلال تمامًا، لذلك تعيد المرحلة 0 الآن الاتصال وتعيد المحاولة بدلًا من الفشل. أما أخطاء التحقق الخاصة بنا (إصدار/معمارية غير مدعومين) فلا تُعاد محاولتها أبدًا. أزال هذا الخطأ المتقطع "stage 0 failed with an empty error" الذي كان يظهر في تشغيل واحد تقريبًا من كل ثلاثة أثناء اختبارات التحمل.

م) تصحيح sizeof(streamNACK) لإصدارات 8.6.x. كان مسار --vuln-type stream يرشّ فئة الحجم الخاطئة في jemalloc لأن البنية كانت مفترضةً على أنها 24 أو 32 بايتًا. في الإصدار 8.6.2 تبلغ 64 بايتًا (delivery_time, delivery_count, consumer, cgroup_ref_node, streamID id, pel_prev, pel_next). مع الحجم الصحيح، يصل مسار stream الآن إلى المرحلة 5 بدلًا من الفشل في المرحلة 2 مع رسالة "key overlap not found".

القيود المعروفة

  • --vuln-type stream غير موثوق على 8.6.2. مع إصلاح الحجم، يمر عبر التحرير المزدوج، والتداخل، وبدائية القراءة/الكتابة، وفحص ELF، ثم يزعزع استقرار فضاء المفاتيح: يموت الخادم في setrangeCommand أثناء قراءة o->ptr عند NULL+8، أي أن البحث عن مفتاح يعيد كائنًا مُفسدًا. القطعة البالغة 64 بايتًا التي يحررها مشتركة مع تخصيصات حية أخرى، مما يجعلها أثقل بكثير من حيث الأضرار الجانبية من مسار zipmap. استخدم --vuln-type zipmap الافتراضي، الذي يحقق 13/13.
  • --random-heap-massage (100 ألف مفتاح عشوائي أولًا) ينجح ولكن ليس دائمًا — أحيانًا تضع الكومة المرشوشة القطعة المحرَّرة مرتين في مكان لا يصل إليه أي مفتاح علامة. إعادة التشغيل تنجح.
  • تم التحقق على aarch64 / Rocky Linux 8.10 / Redis 8.6.2 (غير PIE ET_EXEC) فقط. مسارا x86-64 وPIE منفذان ومحايدان تجاه المعمارية بطبيعة بنائهما لكن لم يُنفذا ضد هدف حي في هذه الجلسة.
تنزيل الأداة