Pythonで一部のHTMLタグをエスケープする

セキュリティーなどの都合で、フォームから送信されてきた文字列の中にHTMLやjavascript,cssの文字列があったらエスケープしたい。 というのをPythonでやってみます。

環境

Python 3.7.1
bleach==3.1.0

HTMLのエスケープにはbleachというライブラリが便利です。

bleachドキュメント

インストール

pip install bleach

ちなみに、bleachはMozillaが開発をしているそうです。

エスケープする

使い方はごく簡単、エスケープしたい文字列をclean関数に渡してあげます。

import bleach

text = """
a<div title="sample" onclick="alert(0)" style="color:red;">b<script>alert(1)</script>c</div>d
"""

bleach.clean(text)

# '\na&lt;div title="sample" onclick="alert(0)" style="color:red;"&gt;b&lt;script&gt;alert(1)&lt;/script&gt;c&lt;/div&gt;d\n'

削除する

先ほどの方法では、HTMLのタグが実体参照に変換されましたが、clean関数の引数にstrip=Trueを指定するとHTMLのタグ部分が削除されます。

text = """
a<div title="sample" onclick="alert(0)" style="color:red;">b<script>alert(1)</script>c</div>d
"""

bleach.clean(text, strip=True)
# '\nabalert(1)cd\n'

ホワイトリストを指定する

先ほどまでは、ほとんどのHTMLタグがエスケープや削除の対象になりましたが、中にはmarkdown等との組み合わせの都合で、エスケープや削除の対象にしたくないタグもあるかもしれません。

そんな時は、ホワイトリストを指定してあげることで特定のタグのみエスケープや削除の対象外にすることもできます。

text = """
a<div title="sample" onclick="alert(0)" style="color:red;">b<script>alert(1)</script>c</div>d
"""

bleach.clean(text, tags=["div", "br", "p"])

# '\na<div>b&lt;script&gt;alert(1)&lt;/script&gt;c</div>d\n'

タグだけでなく属性、スタイルもホワイトリスト指定できます。

text = """
a<div title="sample" onclick="alert(0)" style="color:red;">b<script>alert(1)</script>c</div>d
"""

bleach.clean(text, tags=["div", "br", "p"], attributes={"div":["title", "style"]}, styles=["color"]) # stylesを指定するときは、タグのstyle属性をホワイトリストに入れる必要がある

# '\na<div style="color: red;" title="sample">b&lt;script&gt;alert(1)&lt;/script&gt;c</div>d\n'

実は最初に紹介したシンプルにclean関数にエスケープ対象の文字列だけを渡す方法では、いくつかのHTMLタグはエスケープの対象から外れています。

デフォルトでは、以下のHTMLタグがエスケープの対象外

bleach.ALLOWED_TAGS
# ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul']
bleach.ALLOWED_ATTRIBUTES
# {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']}
bleach.ALLOWED_STYLES
# []  スタイルはデフォルトだと何も許容されていない模様

まとめてホワイトリストに指定する

ワイルドカード

属性を許可する際に、全てのタグに対して同じ属性を許可したい場合、ワイルドカード*が利用できます。

text2 = '<div title="a" style="color: blue;"></div> <span title="b"></span> <p title="c"></p>'

bleach.clean(text2, tags=['div', 'span', 'p'], attributes={'*': ['title']})
# '<div title="a"></div> <span title="b"></span> <p title="c"></p>'

関数で条件を指定する

ワイルドカード指定以外にも関数を渡して条件を指定することもできます。

タグ名と属性名、属性の値の三つを引数にとる関数を用意します。
下の例ではonで始まるハンドラ系の属性を削っています。

def not_on_attribute(tag, name, value):
  return not name.startswith("on")

text3 = '<div title="a" onclick="alert(1)" onmouseover="alert(2)">a</div>'

bleach.clean(text3, tags=['div'], attributes=not_on_attribute)
# '<div title="a">a</div>'