摘要:在本文中我們將解決一些用于生成的模板引擎需要面對的一些安全問題。整個系列的所有文章地址讓我們一起來構建一個模板引擎一讓我們一起來構建一個模板引擎二讓我們一起來構建一個模板引擎三讓我們一起來構建一個模板引擎四文章中涉及的代碼已經(jīng)放到上了
在 上篇文章 中我們的模板引擎實現(xiàn)了對 include 和 extends 的支持, 到此為止我們已經(jīng)實現(xiàn)了模板引擎所需的大部分功能。 在本文中我們將解決一些用于生成 html 的模板引擎需要面對的一些安全問題。
轉義首先要解決的就是轉義問題。到目前為止我們的模板引擎并沒有對變量和表達式結果進行轉義處理, 如果用于生成 html 源碼的話就會出現(xiàn)下面這樣的問題 ( template3c.py ):
>>> from template3c import Template >>> t = Template("{{ title }}
") >>> t.render({"title": "hello
world"}) "hello
"
world
很明顯 title 中包含的標簽需要被轉義,不然就會出現(xiàn)非預期的結果。 這里我們只對 & " " > < 這幾個字符做轉義處理,其他的字符可根據(jù)需要進行處理。
html_escape_table = { "&": "&", """: """, """: "'", ">": ">", "<": "<", } def html_escape(text): return "".join(html_escape_table.get(c, c) for c in text)
轉義效果:
>>> html_escape("hello
world") "hello
world"
既然有轉義自然也要有禁止轉義的功能,畢竟不能一刀切否則就喪失靈活性了。
class NoEscape: def __init__(self, raw_text): self.raw_text = raw_text def escape(text): if isinstance(text, NoEscape): return str(text.raw_text) else: text = str(text) return html_escape(text) def noescape(text): return NoEscape(text)
最終我們的模板引擎針對轉義所做的修改如下(可以下載 template4a.py ):
class Template: def __init__(self, ..., auto_escape=True): ... self.auto_escape = auto_escape self.default_context.setdefault("escape", escape) self.default_context.setdefault("noescape", noescape) ... def _handle_variable(self, token): if self.auto_escape: self.buffered.append("escape({})".format(variable)) else: self.buffered.append("str({})".format(variable)) def _parse_another_template_file(self, filename): ... template = self.__class__( ..., auto_escape=self.auto_escape ) ... class NoEscape: def __init__(self, raw_text): self.raw_text = raw_text html_escape_table = { "&": "&", """: """, """: "'", ">": ">", "<": "<", } def html_escape(text): return "".join(html_escape_table.get(c, c) for c in text) def escape(text): if isinstance(text, NoEscape): return str(text.raw_text) else: text = str(text) return html_escape(text) def noescape(text): return NoEscape(text)
效果:
>>> from template4a import Template >>> t = Template("exec 的安全問題{{ title }}
") >>> t.render({"title": "hello
world"}) "hello
" >>> t = Template("
world{{ noescape(title) }}
") >>> t.render({"title": "hello
world"}) "hello
" >>>
world
由于我們的模板引擎是使用 exec 函數(shù)來執(zhí)行生成的代碼的,所有就需要注意一下 exec 函數(shù)的安全問題,預防可能的服務端模板注入攻擊(詳見 使用 exec 函數(shù)時需要注意的一些安全問題 )。
首先要限制的是在模板中使用內置函數(shù)和執(zhí)行時上下文變量( template4b.py ):
class Template: ... def render(self, context=None): """渲染模版""" namespace = {} namespace.update(self.default_context) namespace.setdefault("__builtins__", {}) # <--- if context: namespace.update(context) exec(str(self.code_builder), namespace) result = namespace[self.func_name]() return result
效果:
>>> from template4b import Template >>> t = Template("{{ open("/etc/passwd").read() }}") >>> t.render() Traceback (most recent call last): File "", line 1, in File "/Users/mg/develop/lsbate/part4/template4b.py", line 245, in render result = namespace[self.func_name]() File " ", line 3, in __func_name NameError: name "open" is not defined
然后就是要限制通過其他方式調用內置函數(shù)的行為:
>>> from template4b import Template >>> t = Template("{{ escape.__globals__["__builtins__"]["open"]("/etc/passwd").read()[0] }}") >>> t.render() "#" >>> >>> t = Template("{{ [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == "_wrap_close"][0].__init__.__globals__["path"].os.system("date") }}") >>> t.render() Mon May 30 22:10:46 CST 2016 "0"
一種解決辦法就是不允許在模板中訪問以下劃線 _ 開頭的屬性。 為什么要包括單下劃線呢,因為約定單下劃線開頭的屬性是約定的私有屬性, 不應該在外部訪問這些屬性。
這里我們使用 dis 模塊來幫助我們解析生成的代碼,然后再找出其中的特殊屬性
這里我們使用 tokenize 模塊來幫助我們解析生成的代碼,然后再找出其中的特殊屬性。
import io import tokenize class Template: def __init__(self, ..., safe_attribute=True): ... self.safe_attribute = safe_attribute def render(self, ...): ... code = str(self.code_builder) if self.safe_attribute: check_unsafe_attributes(code) exec(code, namespace) func = namespace[self.func_name] def check_unsafe_attributes(s): g = tokenize.tokenize(io.BytesIO(s.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
效果:
>>> from template4c import Template >>> t = Template("{{ [x for x in [].__class__.__base__.__subclasses__() if x.__name__ == "_wrap_close"][0].__init__.__globals__["path"].os.system("date") }}") >>> t.render() Traceback (most recent call last): File "", line 1, in File "/xxx/lsbate/part4/template4c.py", line 250, in render check_unsafe_attributes(func) File "/xxx/lsbate/part4/template4c.py", line 296, in check_unsafe_attributes raise AttributeError(msg) AttributeError: access to attribute "__class__" is unsafe. >>> >>> t = Template(" {{ title }}
") >>> t.render({"title": "hello
world"}) "hello
"
world
這個系列的文章到目前為止就已經(jīng)全部完成了。
如果大家感興趣的話可以嘗試使用另外的方式來解析模板內容, 即: 使用詞法分析/語法分析的方式來解析模板內容(歡迎分享實現(xiàn)過程)。
P.S. 整個系列的所有文章地址:
讓我們一起來構建一個模板引擎(一)
讓我們一起來構建一個模板引擎(二)
讓我們一起來構建一個模板引擎(三)
讓我們一起來構建一個模板引擎(四)
P.S. 文章中涉及的代碼已經(jīng)放到 GitHub 上了: https://github.com/mozillazg/lsbate
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/45470.html
摘要:在上篇文章中我們的模板引擎實現(xiàn)了對和對支持,同時在文章的最后我給大家留了一個問題如何實現(xiàn)支持和的標簽功能。在本篇文章中我們將一起來動手實現(xiàn)這兩個功能。 在 上篇文章 中我們的模板引擎實現(xiàn)了對 if 和 for 對支持,同時在文章的最后我給大家留了一個 問題:如何實現(xiàn)支持 include 和 extends 的標簽功能。 在本篇文章中我們將一起來動手實現(xiàn)這兩個功能。 include in...
摘要:回到純靜態(tài)頁面開發(fā)階段,讓頁面不需要后端渲染也能跑起來。改造開始本文著重介紹如何將靜態(tài)頁面改造成后端渲染需要的模板??偨Y在后端渲染的項目里使用多頁應用架構是絕對可行的,可不要給老頑固們嚇唬得又回到傳統(tǒng)前端架構了。 本文首發(fā)于Array_Huang的技術博客——實用至上,非經(jīng)作者同意,請勿轉載。原文地址:https://segmentfault.com/a/119000000820338...
摘要:回到純靜態(tài)頁面開發(fā)階段,讓頁面不需要后端渲染也能跑起來。改造開始本文著重介紹如何將靜態(tài)頁面改造成后端渲染需要的模板。總結在后端渲染的項目里使用多頁應用架構是絕對可行的,可不要給老頑固們嚇唬得又回到傳統(tǒng)前端架構了。 本文首發(fā)于Array_Huang的技術博客——實用至上,非經(jīng)作者同意,請勿轉載。原文地址:https://segmentfault.com/a/119000000820338...
摘要:原作者唐斌騰訊什么原名是一個簡單易用的前端模板預編譯工具。本文作者為來自騰訊團隊的唐斌,他在本文中為我們分析了傳統(tǒng)前端模板內嵌的弊端,如開發(fā)調試效率低下自動化構建復雜度比較高等特點,并針對目前現(xiàn)狀給出了較好的解決方案。 原作者: 唐斌(騰訊)| TmodJS什么 TmodJS(原名atc)是一個簡單易用的前端模板預編譯工具。它通過預編譯技術讓前端模板突破瀏覽器限制,實現(xiàn)后端模板一樣的同...
摘要:使用技術我們將使用將模板編譯為代碼的方式來解析和渲染模板。下面我們就一起來實現(xiàn)這個方法。 假設我們要生成下面這樣的 html 字符串: welcome, Tom age: 20 weight: 100 height: 170 要求姓名以及 中的內容是根據(jù)變量動態(tài)生成的,也就是這樣的: welco...
閱讀 2660·2021-11-18 10:07
閱讀 1122·2021-08-03 14:04
閱讀 750·2019-08-30 13:08
閱讀 2604·2019-08-29 15:33
閱讀 1123·2019-08-29 14:07
閱讀 3024·2019-08-29 14:04
閱讀 1469·2019-08-29 11:19
閱讀 1172·2019-08-29 10:59