
من التصحيح إلى تنفيذ التعليمات البرمجية عن بُعد: استغلال مُصمَّم يدويًا لثغرة CVE-2010-4221 (تجاوز سعة المكدس في TELNET IAC في ProFTPD)، مع توثيق كامل لرحلة الفشل التي قادت إلى النجاح
مختبر قابل للتكرار بالكامل واستغلال مكتوب يدويًا عبر المقابس الخام لـ
CVE-2010-4221 — تجاوز سعة المخزن المؤقت للمكدس قبل المصادقة في
دالة pr_netio_telnet_gets() الخاصة بـ ProFTPD — مبني كتمرين تعليمي في
أبحاث الثغرات وتطوير الاستغلال.
كل فشل موثق. المسار السعيد كذبة؛ والانعطافات هي الدرس.
هذا المستودع أداة تعليمية. إنه موجود ليتعلم منه الأشخاص الذين لا يستطيعون تحمل تكلفة مرشد أو تدريب كيف يولد استغلال تلف الذاكرة فعليًا: من التصحيح، مرورًا بالفشل، وصولًا إلى إثبات مفهوم يعمل داخل مختبر تملكه.
عمل الفريق الأحمر — الأمن الهجومي الاحترافي الحقيقي — يُعرَّف بكلمة واحدة: التفويض. كل ما يفعله المحترف يحدث داخل اتفاقية مكتوبة: وثيقة قواعد اشتباك موقعة تحدد النطاق والأهداف والتقنيات المسموح بها والإطار الزمني والأشخاص الذين وافقوا عليها. بدون تلك الورقة، نفس ضغطات المفاتيح ليست مهنة — بل جريمة في كل ولاية قضائية على وجه الأرض تقريبًا.
إذن هذا هو العقد لهذا المستودع، غير قابل للتفاوض:
الحرفة تستحق التعلم. والحرفة لا تساوي شيئًا إلا بالانضباط الذي يرافقها.
يتحدث ProFTPD بتسلسلات هروب TELNET على قناة التحكم في FTP. في
TELNET، 0xFF (IAC، "فسّر كأمر") هو بايت الهروب؛ و
0xFF الحرفي يُرسل كـ 0xFF 0xFF.
تنسخ دالة pr_netio_telnet_gets() بايتات العميل إلى مخزن مؤقت على المكدس
(char buf[PR_DEFAULT_CMD_BUFSZ+1] الخاص بـ pr_cmd_read، 4104 بايت مع
MAXPATHLEN=4096 الخاص بـ glibc)، متتبعة المساحة المتبقية في buflen — وهو
size_t، غير موقّع.
المسار الضعيف (1.3.3a، netio.c):
case TELNET_IAC:
switch (cp) {
...
default:
*bp++ = TELNET_IAC; // كتابة #1
buflen--; // إنقاص #1
telnet_mode = 0;
break;
}
break;
...
*bp++ = cp; // كتابة #2
buflen--; // إنقاص #2 <-- لا يوجد فحص بينهما
كتابتان، إنقاصان، لا فحص صفري بينهما. عندما تكون buflen
بالضبط 1، ينقصها الزوج إلى 0، ثم يفيض تحتًا إلى
SIZE_MAX (18 كوينتيليون). تعتقد الحلقة الآن أن المخزن المؤقت لا نهائي
وتستمر في كتابة بايتات يتحكم بها المهاجم لأعلى المكدس — فوق السجلات
المحفوظة وRBP المحفوظ وعنوان الإرجاع.
قبل المصادقة. تعمل الدالة قبل معالجة USER/PASS على الإطلاق.
الإصلاح (الالتزام
3cc69b8388،
"Bug#3521 - Telnet IAC processing stack overflow"، صدر في 1.3.3c)
هو اثنا عشر سطرًا. الحد الأمني بأكمله هو:
if (buflen == 0) {
break;
}
انظر patch.diff. قراءة التصحيح تخبرك أين كان الجرح — هذه هي المهارة.
يقوم ملف Dockerfile بتجميع ProFTPD 1.3.3a من مصدر لقطة Debian التاريخية، غير آمن عمدًا (هكذا كان شكل 2010):
-fno-stack-protector — بدون كاناري-z execstack — مكدس قابل للتنفيذ (بدون NX)-no-pie — عناوين ثنائية ثابتةgdb، الذي يعطل ASLR افتراضيًا → مكدس حتميdocker build -t proftpd-133a .
docker rm -f lab133 2>/dev/null
docker run -d --name lab133 --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined -p 127.0.0.1:2122:21 \
proftpd-133a sh -c 'gdb -batch -ex "set follow-fork-mode child" \
-ex "run" -ex "continue" --args /usr/local/sbin/proftpd -n -d1 \
> /tmp/gdb.txt 2>&1; sleep 600'
python3 exploit.py
المخرجات المتوقعة:
[S] 220 ProFTPD 1.3.3a Server (lab-iac) ...
[S] THE SERVER SAID: b'PWNED!!PWNED!!'
(تكتب الشيلكود إلى fds 0 و1 و2 لأننا لم نرغب في الاعتماد على معرفة أي منها يحمل قناة التحكم — اثنان منها يستجيبان.)
"SITE " + زلاجة NOP + شيلكود + [فيضان \xff\xff] + حشو + [ret] + "\n"
^^^^^^^^^^^^^^^^^^^^ ^^^^
الشيلكود يعيش داخل المخزن المؤقت للأمر — المنطقة التي لا يلمسها أحد
ذيل الفيضان يوصل عنوانًا واحدًا فقط
buf[4102]
(NUL الاقتطاع). كل شيء أسفل المتغيرات المحلية الحية للإطار هادئ.buflen إلى الفيضان التحتي (انظر "الرحلة"
لمشكلة التكافؤ).pr_cmd_read إلى return 0 بعد التحليل،
تهبط وحدة المعالجة المركزية في الزلاجة وتنزلق إلى الشيلكود.هذه البنية المعكوسة — الحمولة أولاً، الفيضان ثانيًا، العنوان أخيرًا —
تم التحقق منها مقابل وحدة Metasploit الأساسية
(proftp_telnet_iac)، التي تستخدم نفس التخطيط. كانت أهدافهم تحتوي على NX،
لذا احتاجوا إلى سلسلة ROP مع "إلغاء مرجع رباعي" لمؤشر res؛
مختبرنا لديه مكدس قابل للتنفيذ، لذا يكفي إرجاع مباشر واحد.
الاستغلال النهائي 60 سطرًا. ما كلفه:
فيضان \xff أعمى → لا شيء. أغلق الخادم الجلسة بأدب.
السبب الجذري: تبدأ buflen عند 4102 (زوجي) وكل زوج IAC
ينقص بمقدار 2 — تهبط على 0 بشكل نظيف، أبدًا على 1. الفيضان
التحتي يحتاج كسر التكافؤ. الدرس: قراءة آلة الحالة تتفوق على الرش.
حجم مخزن مؤقت خاطئ. افترضت المحاولة المعايرة الأولى مخزنًا مؤقتًا
بحجم 1024 بايت. الحقيقي هو MAXPATHLEN+8 = 4104 على Linux/glibc. توقف
الفيضان على بعد 3 كيلوبايت من الهدف. الدرس: قس الهدف، لا
تفترض الهدف.
أول SIGSEGV. وضع نمط de Bruijn الدوري (Aa0Aa1...) فتحة
الإرجاع عند buf+4152، تم التحقق منه بالتبادل مرتين (حساب الإطار + إزاحة
النمط). الدرس: النمط الدوري شريط قياس، ليس
استغلالًا.
التحكم في RIP. ضبط الفتحة على 0x4141414141414141 عطّل
تعليمة ret نفسها — يرفض x86-64 العناوين غير القانونية، و
يقع الخطأ على ret، مع قيمة تنتظر في تتبع المكدس.
الدرس: تعطل على ret مع قيمتك في الإطار = تحكم.
شيلكود فوق فتحة ret → تم إتلافه. تم استبدال 8 بايتات بمؤشر
كومة (0x4d7838 — تم تحديده لاحقًا كتخصيص تجمع cmd_rec).
إطارات المكدس فوق الفتحة تنتمي إلى دوال لا تزال تعمل بين الهبوط والاختطاف.
الدرس: الفيضان ليس آخر كتابة؛ يستمر البرنامج في العيش على المكدس الذي خربته للتو.
"المنطقة الميتة" أسفل الفتحة → أيضًا تم خربشتها. المتغيرات المحلية
الخاصة بـ pr_cmd_read (cmd، buflen، cp) تعيش هناك تمامًا
وتستمر في التخزين أثناء التحليل.
تحقيقات نقطة المراقبة العتادية. watch *(long*)ADDR في gdb حوّل
اللغز إلى كاميرا: كل كتابة على العنوان المتلف، مع
تتبع المكدس، بالترتيب. الدرس: عندما يكون السؤال "من كتب هذه
الذاكرة؟"، تكون الإجابة على بعد أمر gdb واحد.
اقرأ المرجع، ثم افهمه. أكدت الوحدة الأساسية البنية المعكوسة. قراءة استغلال آخر بعد بناء نموذجك العقلي الخاص هي دراسة؛ قبلها، هي نسخ.
size_t لا يصبح سالبًا أبدًا — يصبح هائلًا. الفيضان التحتي الصحيح
في عداد المساحة المتبقية هو تجاوز مكدس بخطوات إضافية.\x0a
(ينهي القراءة) وتنجو من \xff (هروب TELNET) بالتصميم.argv[0] (ثنائي شجرة البناء مقابل الثنائي المثبت) أزاح المكدس
بأكمله بمقدار 0x40 وأبطل استغلالًا مثاليًا بصمت.modules/exploits/linux/ftp/proftp_telnet_iac.rb
(rapid7/metasploit-framework)بُني بواسطة diegslva، يتعلم علنًا — من "لم يكتب استغلالًا أبدًا" إلى RCE قبل المصادقة بشيلكود مكتوب يدويًا، في يوم واحد موثق. إذا كان هذا المستودع علمك شيئًا، رد الجميل.