摘要:如果一定要用的話,那么就需要注意一下下面這些安全相關的問題。全局變量和內置函數在執行的代碼中,默認可以訪問執行時的局部變量和全局變量,同樣也會修改全局變量。所以我們的檢查代碼可以這樣寫我所知道的使用函數時需要注意的安全問題就是這些了。
眾所周知,在 python 中可以使用 exec 函數來執行包含 python 源代碼的字符串:
>>> code = """ ...: a = "hello" ...: print(a) ...: """ >>> exec(code) hello >>> a "hello"
exec 函數的這個功能很是強大,慎用。如果一定要用的話,那么就需要注意一下下面這些安全相關的問題。
全局變量和內置函數在 exec 執行的代碼中,默認可以訪問執行 exec 時的局部變量和全局變量, 同樣也會修改全局變量。如果 exec 執行的代碼是根據用戶提交的數據生產的話,這種默認行為就是一個安全隱患。
如何更改這種默認行為呢?可以通過執行 exec 函數的時候再傳兩個參數的方式來 修改這種行為(詳見 之前 關于 exec 的文章):
>>> g = {} >>> l = {"b": "world"} >>> exec("hello = "hello" + b", g, l) >>> l {"b": "world", "hello": "helloworld"} >>> g {"__builtins__": {...}} >>> hello --------------------------------------------------------------------------- NameError Traceback (most recent call last) ... NameError: name "hello" is not defined
如果要限制使用內置函數的話,可以在 globals 參數中定義一下 __builtins__ 這個 key:
>>> g = {} >>> l = {} >>> exec("a = int("1")", g, l) >>> l {"a": 1} >>> g = {"__builtins__": {}} >>> exec("a = int("1")", g, l) Traceback (most recent call last): File "", line 1, in File " ", line 1, in NameError: name "int" is not defined >>>
現在我們限制了訪問和修改全局變量以及使用內置函數,難道這樣就萬事大吉了嗎? 然而并非如此,還是可以通過其他的方式來獲取內置函數甚至 os.system 函數。
另辟蹊徑獲取內置函數和 os.system通過函數對象:
>>> def a(): pass ... >>> a.__globals__["__builtins__"] >>> a.__globals__["__builtins__"].open
通過內置類型對象:
>>> for cls in {}.__class__.__base__.__subclasses__(): ... if cls.__name__ == "WarningMessage": ... b = cls.__init__.__globals__["__builtins__"] ... b["open"] ...>>>
獲取 os.system:
>>> cls = [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == "_wrap_close"][0] >>> cls.__init__.__globals__["path"].os>>>
對于這兩種辦法又如何應對呢? 一種辦法就是禁止訪問以 _ 開頭的屬性:
如果可以控制 code 的生成,那么就在生成 code 的時候判斷
如果不能的話,可以通過 dis 模塊分析生成的 code (dist 無法分析嵌套函數的代碼)
使用 tokenize 模塊:
In [68]: from io import BytesIO In [69]: code = """ ....: a = "b" ....: a.__str__ ....: def b(): ....: b.__get__ ....: """ In [70]: t = tokenize(BytesIO(code.encode()).readline) In [71]: for x in t: ....: print(x) ....: TokenInfo(type=59 (ENCODING), string="utf-8", start=(0, 0), end=(0, 0), line="") TokenInfo(type=58 (NL), string=" ", start=(1, 0), end=(1, 1), line=" ") TokenInfo(type=1 (NAME), string="a", start=(2, 0), end=(2, 1), line="a = "b" ") TokenInfo(type=53 (OP), string="=", start=(2, 2), end=(2, 3), line="a = "b" ") TokenInfo(type=3 (STRING), string=""b"", start=(2, 4), end=(2, 7), line="a = "b" ") TokenInfo(type=4 (NEWLINE), string=" ", start=(2, 7), end=(2, 8), line="a = "b" ") TokenInfo(type=1 (NAME), string="a", start=(3, 0), end=(3, 1), line="a.__str__ ") TokenInfo(type=53 (OP), string=".", start=(3, 1), end=(3, 2), line="a.__str__ ") TokenInfo(type=1 (NAME), string="__str__", start=(3, 2), end=(3, 9), line="a.__str__ ") TokenInfo(type=4 (NEWLINE), string=" ", start=(3, 9), end=(3, 10), line="a.__str__ ") TokenInfo(type=1 (NAME), string="def", start=(4, 0), end=(4, 3), line="def b(): ") TokenInfo(type=1 (NAME), string="b", start=(4, 4), end=(4, 5), line="def b(): ") TokenInfo(type=53 (OP), string="(", start=(4, 5), end=(4, 6), line="def b(): ") TokenInfo(type=53 (OP), string=")", start=(4, 6), end=(4, 7), line="def b(): ") TokenInfo(type=53 (OP), string=":", start=(4, 7), end=(4, 8), line="def b(): ") TokenInfo(type=4 (NEWLINE), string=" ", start=(4, 8), end=(4, 9), line="def b(): ") TokenInfo(type=5 (INDENT), string=" ", start=(5, 0), end=(5, 4), line=" b.__get__ ") TokenInfo(type=1 (NAME), string="b", start=(5, 4), end=(5, 5), line=" b.__get__ ") TokenInfo(type=53 (OP), string=".", start=(5, 5), end=(5, 6), line=" b.__get__ ") TokenInfo(type=1 (NAME), string="__get__", start=(5, 6), end=(5, 13), line=" b.__get__ ") TokenInfo(type=4 (NEWLINE), string=" ", start=(5, 13), end=(5, 14), line=" b.__get__ ") TokenInfo(type=6 (DEDENT), string="", start=(6, 0), end=(6, 0), line="") TokenInfo(type=0 (ENDMARKER), string="", start=(6, 0), end=(6, 0), line="")
從上面的輸出我們可以知道當 type 是 OP 并且 string 等于 "." 時,下一條記錄就是
點之后的屬性名稱。所以我們的檢查代碼可以這樣寫:
import io import tokenize def check_unsafe_attributes(string): g = tokenize.tokenize(io.BytesIO(string.encode("utf-8")).readline) pre_op = "" for toktype, tokval, _, _, _ in g: if toktype == tokenize.NAME and pre_op == "." and tokval.startswith("_"): attr = tokval msg = "access to attribute "{0}" is unsafe.".format(attr) raise AttributeError(msg) elif toktype == tokenize.OP: pre_op = tokval
我所知道的使用 exec 函數時需要注意的安全問題就是這些了。 如果你還知道其他需要注意的安全問題的話,歡迎留言告知。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/37950.html
摘要:如果一定要用的話,那么就需要注意一下下面這些安全相關的問題。全局變量和內置函數在執行的代碼中,默認可以訪問執行時的局部變量和全局變量,同樣也會修改全局變量。所以我們的檢查代碼可以這樣寫我所知道的使用函數時需要注意的安全問題就是這些了。 眾所周知,在 python 中可以使用 exec 函數來執行包含 python 源代碼的字符串: >>> code = ...: a = hel...
摘要:內置函數們能夠被提拔出來,這就意味著它們皆有獨到之處,有用武之地。因此,掌握內置函數的用法,就成了我們應該點亮的技能。報錯包含了內置命名空間中的名稱,在控制臺中輸入,就能發現很多內置函數異常和其它屬性的名稱。 Python 提供了很多內置的工具函數(Built-in Functions),在最新的 Python 3 官方文檔中,它列出了 69 個。 大部分函數是我們經常使用的,例如 p...
摘要:在本文中我們將解決一些用于生成的模板引擎需要面對的一些安全問題。整個系列的所有文章地址讓我們一起來構建一個模板引擎一讓我們一起來構建一個模板引擎二讓我們一起來構建一個模板引擎三讓我們一起來構建一個模板引擎四文章中涉及的代碼已經放到上了 在 上篇文章 中我們的模板引擎實現了對 include 和 extends 的支持, 到此為止我們已經實現了模板引擎所需的大部分功能。 在本文中我們將解...
摘要:相同點都可以獲得命令執行的狀態碼作為一種服務器端的腳本語言,象編寫簡單,或者是復雜的動態網頁這樣的任務,它完全能夠勝任。于是的設計者們給加了一個門安全模式。第二個參數是可選的,用來得到命令執行后的狀態碼。 詳細的介紹了關于PHP exec system passthru系統函數用法與安全以及其它應用功能,有需要的朋友參考一下。區別:system() 輸出并返回最后一行shell結果。e...
摘要:共享資源臨界資源修飾實例方法輸出結果上述代碼與前面不同的是我們同時創建了兩個新實例,然后啟動兩個不同的線程對共享變量進行操作,但很遺憾操作結果是而不是期望結果。 線程安全是并發編程中的重要關注點,應該注意到的是,造成線程安全問題的主要誘因有兩點 一是存在共享數據(也稱臨界資源) 二是存在多條線程共同操作共享數據 因此為了解決這個問題,我們可能需要這樣一個方案,當存在多個線程操作共享...
閱讀 2471·2021-11-23 09:51
閱讀 531·2019-08-30 13:59
閱讀 1830·2019-08-29 11:20
閱讀 2538·2019-08-26 13:41
閱讀 3246·2019-08-26 12:16
閱讀 735·2019-08-26 10:59
閱讀 3330·2019-08-26 10:14
閱讀 605·2019-08-23 17:21