在 高可擴展的任務執行架構需求 一文中,山姆鍋提到基於 Python 的超輕量級沙盒(sandbox)。沙盒是一個有限制的執行環境, 用來執行不受信任的程式碼/腳本(script)。本文山姆鍋基於需要,實驗一個 Python 沙盒的開源專案。
請注意:按照該開源專案所述,不保證所提供的方法一定安全。
為什麼需要沙盒環境?
有時候應用軟體為了彈性起見,會允許使用者自己撰寫腳本來擴充軟體功能。由於不能預期使用者撰寫的內容, 如果沒有適當控制的話,小則讓程式崩潰,大則出現安全漏洞。為了解決這樣的問題, 有人提出讓不受信任的程式在一個沙盒環境中執行。就像小孩子玩沙一樣,只要不超出沙盒的範圍, 基本上不用擔心安全問題。
但要在 Python (精確講是 CPython
)實作一個安全的沙盒環境並不是那麼簡單。使用 Python 內建的
eval
衍生的安全問題
眾所皆知 ⎘
。
沙盒環境的需求
為了安全原因,沙盒中的程式允許執行的功能是越少越好。在 高可擴展的任務執行架構需求 文章所提的任務執行服務,其中代理人的邏輯便是由腳本實現。代理人的功能相對單純,只需要邏輯判斷跟呼叫平台提供的函式完成任務。 由於複雜跟不安全的操作都由平台提供,針對代理人的腳本,山姆鍋的需求只有:
- 支援運算式。
- 支援條件式、以及有限制的 for 迴圈流程控制。
除此之外的功能全部都要禁止使用!
沙盒環境的實作
上面提到使用 Python 的 eval
來執行不受信任的程式是非常危險的行為。那有沒有其他較安全的方案?
俗話說得好:「萬事問谷歌大神讓你變聰明」(誤)。不管如何,山姆鍋找到一個有希望的開源專案:
asteval ⎘ 。
「工欲善其事,必先利其器」,讓我們進一步來了解 asteval
的工作原理。
asteval 的運作原理
asteval
的運作的基本原理如下:
- 使用 Python 內建的
ast
模組先將腳本剖析(parse)成抽象語法樹(Abstract Syntax Tree ; AST)的形式, - 尋訪(visit)語法樹,只執行允許的功能。
感覺還蠻容易理解,對於要求安全性來說這是好事。容易理解才比較能看出哪裏有問題。
使用範例
底下是 asteval 網站提供的範例:
>>> from asteval import Interpreter
>>> aeval = Interpreter()
>>> aeval('x = sqrt(3)')
>>> aeval('print x')
1.73205080757
>>> aeval('''for i in range(10):
print i, sqrt(i), log(1+1)
''')
0 0.0 0.0
1 1.0 0.69314718056
2 1.41421356237 1.09861228867
3 1.73205080757 1.38629436112
4 2.0 1.60943791243
5 2.2360679775 1.79175946923
6 2.44948974278 1.94591014906
7 2.64575131106 2.07944154168
8 2.82842712475 2.19722457734
9 3.0 2.30258509299
延伸思考
- 雖然執行時期一定需要檢查,但在腳本儲存到系統之前似乎也可以先做一次大略的檢查。這樣在沒有實際執行腳本的情況下,就可以過濾掉大部份有問題的程式碼。
- 由於山姆鍋的需求比
asteval
提供的還少,除了原先它不允許的用法之外,還要進一步做限制。例如:
- 不支援 while 迴圈。
- 迴圈數不允許超過 100。
- 迴圈中不允許再包含迴圈。
- 不支援函式定義。
- 不允許 ’**’ 運算子。
- 因為採用防堵的設計,總有可能遇到沒考慮到的情況導致出現漏洞。
結語
根據 asteval
網站提供的資訊,透過它來執行腳本比直接使用
eval
平均慢約 4 倍左右。
另外,這個沙盒環境沒有處理腳本消耗過多資源的問題,例如:‘2**1234567890’
這樣簡單的運算式就足夠讓腳本卡住。
參考資料
Abstract Syntax Tree: https://en.wikipedia.org/wiki/Abstract_syntax_tree ⎘
抽象語法樹。