Sandbox¶
Jinja 的 Sandbox 可用來呈現不可信的範本。對屬性、方法呼叫、運算子、變異資料結構和字串格式化的存取可被攔截並禁止。
>>> from jinja2.sandbox import SandboxedEnvironment
>>> env = SandboxedEnvironment()
>>> func = lambda: "Hello, Sandbox!"
>>> env.from_string("{{ func() }}").render(func=func)
'Hello, Sandbox!'
>>> env.from_string("{{ func.__code__.co_code }}").render(func=func)
Traceback (most recent call last):
...
SecurityError: access to attribute '__code__' of 'function' object is unsafe.
Sandbox 的環境可能會很有用,例如,讓內部報告系統的使用者製作自訂電子郵件。你可以記錄範本中有什麼資料可用,然後使用者會使用這些資訊撰寫範本。你的程式會產生報告資料,並將其傳遞給使用者的 Sandbox 範本進行呈現。
安全性考量¶
僅使用 Sandbox 無法達成完美的安全性。使用 Sandbox 時,請務必記住以下事項。
在編譯或呈現時,範本仍有可能會引發錯誤。你的程式應設法捕捉錯誤,而不是崩潰。
有可能建立一個相對較小的範本,但會呈現大量的輸出,這可能會對應到高使用率的 CPU 或記憶體。你應該對你的應用程式執行資源限制,例如 CPU 和記憶體,以減輕這種情況。
Jinja 只會呈現文字,它不理解,例如,JavaScript 程式碼。根據已呈現範本的使用方式,你可能需要執行其他後處理來限制輸出。
只傳遞與範本相關的資料。避免傳遞全域資料,或具有附帶效果的方法之物件。預設情況下,Sandbox 會禁止私有和內部屬性存取。你可以覆寫 is_safe_attribute()
,以進一步限制屬性存取。使用 unsafe()
裝飾方法,以避免在傳遞物件為資料時,從範本呼叫這些方法。使用 ImmutableSandboxedEnvironment
以避免修改清單和字典。
API¶
- class jinja2.sandbox.SandboxedEnvironment([options])¶
Sandbox 環境。它運作的方式與一般環境相同,但會告訴編譯器產生 Sandbox 程式碼。此外,此環境的子類別可能會覆寫告訴執行階段哪些屬性或函式可以安全存取的方法。
如果範本試圖存取不安全的程式碼,便會引發
SecurityError
。不過,在渲染過程中也可能發生其他例外,因此呼叫端必須確保已捕捉到所有例外。- default_binop_table: 字典[字串, 可呼叫函式[[任意, 任意], 任意]] = {'%': 內建 函數 mod>, '*': 內建 函數 mul>, '**': 內建 函數 pow>, '+': 內建 函數 add>, '-': 內建 函數 sub>, '/': 內建 函數 truediv>, '//': 內建 函數 floordiv>}¶
二元運算子的預設回呼函式表。每個 sandboxed 環境實例中都可使用此表的副本,稱為
binop_table
- default_unop_table: Dict[str, Callable[[Any], Any]] = {'+': <built-in function pos>, '-': <built-in function neg>}¶
一元運算符預設的 callback 表格。在沙箱環境中每個執行個體的副本會放在
unop_table
中
- intercepted_binops: FrozenSet[str] = frozenset({})¶
一組應該被攔截的二元運算符。加入到這組(預設是空的)中的每個運算符都會被委派給
call_binop()
方法,它將執行運算符。預設的運算符 callback 會由binop_table
指定。以下二元運算符可以攔截:
//
、%
、+
、*
、-
、/
和**
運算子表格中預設的操作形式對應到內建函數。攔截的呼叫總是會比原生運算子呼叫更慢,因此請確認只攔截您有興趣的呼叫。
變更記錄
已於版本 2.6 中新增。
- intercepted_unops: FrozenSet[str] = frozenset({})¶
一組應攔截的單元運算子。新增到此組的每個運算子(預設為空)都會委派給
call_unop()
方法來執行此運算子。預設運算子回呼由unop_table
指定。下列單元運算子可進行攔截:
+
,-
運算子表格中預設的操作形式對應到內建函數。攔截的呼叫總是會比原生運算子呼叫更慢,因此請確認只攔截您有興趣的呼叫。
變更記錄
已於版本 2.6 中新增。
- is_safe_attribute(obj, attr, value)¶
沙箱化環境會呼叫此方法,以檢查這個物件的屬性是否安全可以存取。預設值為所有以底線開頭的屬性都會被視為私人屬性,此外也會包含
is_internal_attribute()
函數回傳的 Python 內部物件的特殊屬性。
- is_safe_callable(obj)¶
檢查一個物件是否可安全呼叫。根據預設,可呼叫的物件被視為安全,除非使用
unsafe()
裝飾器。這也識別 Django 慣例設定
func.alters_data = True
。
- call_binop(context, operator, left, right)¶
對於攔截的二元運算子呼叫 (
intercepted_binops()
),這個函式會執行,而不是內建運算子。這可以用來微調特定運算子的行為。變更記錄
已於版本 2.6 中新增。
- 類別 jinja2.sandbox.ImmutableSandboxedEnvironment([選項])¶
與一般
SandboxedEnvironment
功能相同,但透過modifies_known_mutable()
函數,不允許使用list
、set
和dict
這些內建的可變動物件進行變更。
- exception jinja2.sandbox.SecurityError(message=None)¶
如果模板在沙盒啟用的狀態下嘗試做一些不安全的事,就會引發這個錯誤。
- 參數:
message (str | None)
- 傳回型別:
None
- jinja2.sandbox.unsafe(f)¶
將函數或方法標記為不安全。
- 參數:
f (F)
- 傳回型別:
F
- jinja2.sandbox.is_internal_attribute(obj, attr)¶
測試提供的屬性是否為 Python 內部的屬性。例如,對於 Python 物件的
func_code
屬性,此函數會回傳True
。如果覆寫環境方法is_safe_attribute()
,這個會很有用。>>> from jinja2.sandbox import is_internal_attribute >>> is_internal_attribute(str, "mro") True >>> is_internal_attribute(str, "upper") False
- jinja2.sandbox.modifies_known_mutable(obj, attr)¶
此函數會檢查一個內建可變動物件(list、dict、set 或 deque)或其對應的抽象基底類別 (ABC) 上的屬性,如果呼叫該屬性,是否會修改物件。
>>> modifies_known_mutable({}, "clear") True >>> modifies_known_mutable({}, "keys") False >>> modifies_known_mutable([], "append") True >>> modifies_known_mutable([], "index") False
如果輸入不支援的物件,將傳回
False
。>>> modifies_known_mutable("foo", "upper") False
運算子攔截¶
為提升效能,Jinja 在編譯時會直接輸出運算子。這表示預設情況下,無法透過覆寫 SandboxEnvironment.call
來攔截運算子行為,因為運算子特殊方法由 Python 詮釋器處理,且可能無法根據運算子的使用方式對應到完全相同的一個方法。
然而,沙盒環境可以指示編譯器輸出一個函式來攔截某些運算子。改寫 SandboxedEnvironment.intercepted_binops
和 SandboxedEnvironment.intercepted_unops
,並指定您想要攔截的運算子符號。編譯器將會用呼叫 SandboxedEnvironment.call_binop()
和 SandboxedEnvironment.call_unop()
來取代這些符號。這些方法的預設實作將會使用 SandboxedEnvironment.binop_table
和 SandboxedEnvironment.unop_table
將運算子符號轉譯為 operator
函式。
例如,可以停用冪次 (**
) 運算子
from jinja2.sandbox import SandboxedEnvironment
class MyEnvironment(SandboxedEnvironment):
intercepted_binops = frozenset(["**"])
def call_binop(self, context, operator, left, right):
if operator == "**":
return self.undefined("The power (**) operator is unavailable.")
return super().call_binop(self, context, operator, left, right)