使用Python清理用户输入
在一个基于Python的网页应用中,怎样才能最好地处理用户输入,确保安全呢?有没有一个简单的函数可以去掉HTML字符和其他一些必要的字符组合,以防止像跨站脚本攻击(XSS)或者SQL注入攻击这样的安全问题呢?
7 个回答
防止XSS(跨站脚本攻击)的最好方法不是试图过滤所有内容,而是简单地进行HTML实体编码。比如,把<自动转换成<。这个方法是理想的,前提是你不需要接受任何HTML输入(除了论坛或评论区这种用作标记的地方,通常不需要接受HTML);因为通过不同的编码方式,除了非常严格的白名单(比如只允许字母和数字a-z、A-Z、0-9),其他方式都可能会放过一些不安全的内容。
至于SQL注入,虽然有些人认为它不再可能,但如果你只是简单地拼接查询字符串,那还是会有SQL注入的风险。比如,如果你直接把一个传入的参数拼接到查询字符串中,就会导致SQL注入。保护自己免受这种攻击的最好方法同样不是过滤,而是严格使用参数化查询,绝对不要拼接用户输入。
这并不是说过滤就不重要了,它仍然是一个最佳实践。但在防止SQL注入和XSS方面,如果你严格使用参数化查询和HTML实体编码,你会更加安全。
编辑: bleach 是一个基于 html5lib 的工具,它让使用白名单进行清理变得更加简单。
html5lib
自带一个基于白名单的 HTML 清理工具 - 你可以很容易地对它进行扩展,以限制用户在你的网站上可以使用的标签和属性。如果你允许使用 style
属性,它甚至会尝试清理 CSS。
这是我在我的 Stack Overflow 克隆项目中的 sanitize_html
工具函数的使用方法:
http://code.google.com/p/soclone/source/browse/trunk/soclone/utils/html.py
我测试了 ha.ckers.org 的 XSS 备忘单 上列出的所有攻击(这些攻击方便地以 XML 格式 提供),并在使用 python-markdown2 将 Markdown 转换为 HTML 后,似乎都能正常处理。
不过,Stack Overflow 目前使用的 WMD 编辑器组件有点问题 - 我实际上不得不禁用 JavaScript 才能测试 XSS 备忘单的攻击,因为把所有内容粘贴到 WMD 中会让我看到警告框,页面也会变空。
这里有一段代码,可以删除所有不在白名单上的标签,以及所有不在属性白名单上的标签属性(比如你不能使用 onclick
)。
这段代码是对 http://www.djangosnippets.org/snippets/205/ 的修改版,增加了对属性值的正则表达式检查,以防止有人使用 href="javascript:..."
这样的写法,还有其他一些情况可以参考 http://ha.ckers.org/xss.html。
(例如 <a href="ja	vascript:alert('hi')">
或 <a href="ja vascript:alert('hi')">
等等。)
正如你所看到的,这段代码使用了非常棒的 BeautifulSoup 库。
import re
from urlparse import urljoin
from BeautifulSoup import BeautifulSoup, Comment
def sanitizeHtml(value, base_url=None):
rjs = r'[\s]*(&#x.{1,7})?'.join(list('javascript:'))
rvb = r'[\s]*(&#x.{1,7})?'.join(list('vbscript:'))
re_scripts = re.compile('(%s)|(%s)' % (rjs, rvb), re.IGNORECASE)
validTags = 'p i strong b u a h1 h2 h3 pre br img'.split()
validAttrs = 'href src width height'.split()
urlAttrs = 'href src'.split() # Attributes which should have a URL
soup = BeautifulSoup(value)
for comment in soup.findAll(text=lambda text: isinstance(text, Comment)):
# Get rid of comments
comment.extract()
for tag in soup.findAll(True):
if tag.name not in validTags:
tag.hidden = True
attrs = tag.attrs
tag.attrs = []
for attr, val in attrs:
if attr in validAttrs:
val = re_scripts.sub('', val) # Remove scripts (vbs & js)
if attr in urlAttrs:
val = urljoin(base_url, val) # Calculate the absolute url
tag.attrs.append((attr, val))
return soup.renderContents().decode('utf8')
正如其他人所说,几乎所有的 Python 数据库库都能处理 SQL 注入问题,所以这段代码基本上可以满足你的需求。