
🔪 إثبات المفهوم (PoC) لهجوم CRIME : هجوم أوراكل الضغط على CVE-2012-4929 🔪
هجوم CRIME: هو هجوم أوراكل ضغط CVE-2012-4929 اكتشفه جوليانو ريتزو وتاي دونغ؛
في هجوم أوراكل الضغط، يمكن أن يؤدي استخدام ضغط البيانات التكيفي على مزيج من النص الصريح المختار والنص الصريح غير المعروف إلى تغييرات حساسة للمحتوى في طول النص المضغوط يمكن اكتشافها حتى وإن كان محتوى النص المضغوط نفسه مشفرًا بعد ذلك. يمكن استخدام هذا في هجمات البروتوكول لاكتشاف متى يكون النص الصريح المعروف المحقون مشابهًا جزئيًا حتى للمحتوى غير المعروف لجزء سري من الرسالة، مما يقلل بشكل كبير من تعقيد البحث عن تطابق للنص السري. هجوم CRIME وهجوم BREACH مثالان على هجمات البروتوكول التي تستخدم هذه الظاهرة.
يتيح لك هجوم CRIME استرداد البيانات المشفرة التي يرسلها العميل إلى الخادم باستخدام طول البيانات المشفرة. وهو لا يتيح لك استرداد المفتاح الخاص المستخدم لتشفير الرسالة أو طلب HTTP.
تشرح العديد من المقالات كيفية عمل هجوم CRIME، لكن هذه أفضل التفسيرات التي وجدتها على الإنترنت:
هذا الهجوم ليس معقدًا حقًا، فالجزء المثير للاهتمام حقًا هو التنفيذ الذي يختلف قليلًا عن «النظرية».
لنلقِ نظرة على الطريقة الساذجة كما هي موصوفة في المقال، إنها طريقة جيدة لفهم كيفية عمله:
يمكن للمهاجم التحكم في الطلب الذي يرسله العميل (باستخدام جافا سكريبت مثلًا). الهدف هو استرداد ملف تعريف الارتباط السري. يرسل المهاجم طلبات متعددة مثل هذه ويتحقق من طول البيانات المشفرة:
| الطلب | الطول |
|---|---|
| GET /cookie= DATA cookie=quokkalight | 80 |
| GET /cookie=a DATA cookie=quokkalight | 81 |
| GET /cookie=b DATA cookie=quokkalight | 81 |
| GET /cookie=. DATA cookie=quokkalight | 81 |
| GET /cookie=q DATA cookie=quokkalight | 80 |
بما أن cookie=q يطابق cookie=quokkalight من ملف تعريف الارتباط السري، فسيكون طول البيانات المشفرة هو نفسه، ويعلم المهاجم أنه وجد بايتًا.
لكن هذه الطريقة تفشل أحيانًا ولا يمكن الوثوق بها، لذا سنستخدم طريقة أخرى بدلًا من ذلك.
أولًا نرسل طلبًا يحتوي على الحرف الذي نريد العثور عليه متبوعًا بعدة أحرف لا يمكن العثور عليها في الطلب الأولي مثل بعض الأحرف الخاصة: chr(i) + "#:/[@/&". ثم نرسل طلبًا ثانيًا لكننا نعكس الحمولة هكذا: "#:/[@/&" + chr(i) ونقارن بين الطولين. إذا كان len(enc(req1)) < len(enc(req2)) فقد وجدنا بايتًا. تسمى هذه الطريقة: two_tries وهي أكثر موثوقية بكثير:
| الطلب لاسترداد البايت q | الطول |
|---|---|
| GET /cookie=a~#:/[@/& DATA cookie=quokkalight | 81 |
| GET /cookie=~#:/[@/&a DATA cookie=quokkalight | 81 |
| GET /cookie=b~#:/[@/& DATA cookie=quokkalight | 81 |
| GET /cookie=~#:/[@/&b DATA cookie=quokkalight | 81 |
| GET /cookie=q~#:/[@/& DATA cookie=quokkalight | 80 |
| GET /cookie=~#:/[@/&q DATA cookie=quokkalight | 81 |
وجد المهاجم بايتًا!
if len(enc(request1)) < len(enc(request2)):
print("found byte")
طريقة two_tries التي نفذتها تعاودية بالكامل، لكن لماذا؟ في بعض الأحيان، يمكن العثور على أكثر من بايت واحد لأن الضغط يطابق أنماطًا متعددة.
لنأخذ السر: cookie=quokkalight، إذا شغّلنا الخوارزمية two_tries فسنجد النتيجة التالية:
result 1: cookie=quokie=quokie=quokie=quokie=quokie=
result 2: cookie=quokkalight
تحتاج الخوارزمية إلى سلوك جميع المسارات في الشجرة للعثور على جميع الحلول الممكنة. يمكننا رؤية جميع النتائج كشجرة يمكن تمثيلها على النحو التالي:

يمكن العثور على إثبات المفهوم لهجوم CRIME ضد وضع شيفرة التدفق في الملف: CRIME-RC4-poc.py. هذا تنفيذ بلغة بايثون للشرح السابق.
python3 CRIME-RC4-poc.py
عرض توضيحي كامل والنتيجة:
عند استخدام وضع تشفير CBC مع AES أو DES، لا يكون الهجوم بنفس بساطة هجوم RC4. نظرًا لأن كل شيء مقسم إلى كتل، يصبح الهجوم أكثر تعقيدًا قليلًا (ليس كثيرًا).
على سبيل المثال، لنفترض أننا نستخدم AES مع وضع تشفير CBC، فسيتم تقسيم الكتلة إلى طول 16 وسيُضاف حشو إلى النهاية إذا كان len(data)%16 != 0.
في وضع CBC، من المهم ملاحظة أن الحمولة payload=rand ستنتج طولًا قدره 16 وليس 12، لأنه سيُضاف حشو في نهاية البيانات قبل التشفير. لذا لا يمكن أن يعمل هجومنا السابق.
مثال:
| الكتلة 1 | الكتلة 2 | الكتلة 3 | الطول |
|---|---|---|---|
| GET /cookie= DA | TA cookie=quokka | light + PAD(11) | 48 |
| GET /cookie=a DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=b DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=. DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=q DA | TA cookie=quokka | light + PAD(11) | 48 |
في هذا المثال، سيكون الطول هو نفسه دائمًا، لأنه إذا أضفنا أو أزلنا بايتًا، سيبقى الطول كما هو، فقط الحشو سيتغير. يرى المهاجم البيانات المشفرة فقط، ولا توجد لديه طريقة لمعرفة الحشو من البيانات المشفرة.
الحل: العب مع مواصفات وضع CBC، بحيث يصبح طول الحشو 1 عن طريق إضافة قيمة عشوائية في متغير GARB (في معامل GET، حيث يتحكم المهاجم في بيانات GET وPOST).
| الكتلة 1 | الكتلة 2 | الكتلة 3 | الكتلة 4 | الطول |
|---|---|---|---|---|
| GET /GARBc | ookie= DATA coo | kie=quokkalight + PAD(1) | 48 | |
| GET /GARBc | ookie=a DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=b DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=. DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=q DATA coo | kie=quokkalight + PAD(1) | 48 |
إذا تطابق البايت مع نمط، فسيكون الطول هو نفسه، وإذا لم يتطابق، سيكون الطول مختلفًا.
بعد ذلك، علينا فقط استخدام الطريقة نفسها الموضحة في جزء RC4، ولكن نضيف خطوة واحدة قبلها.
adjust_padding() حتى نحصل على حشو بطول 1two_tries_recursive() ويمكننا العثور على FLAG السري!python3 CRIME-cbc-poc.py
قريبًا جدًا...
\ /
\ _ /
----/_\----
x--------------( . )--------------x
x|x | |_|\_/|_| | x|x
x x x x