使用Python清理用户输入

64 投票
7 回答
55067 浏览
提问于 2025-04-11 00:01

在一个基于Python的网页应用中,怎样才能最好地处理用户输入,确保安全呢?有没有一个简单的函数可以去掉HTML字符和其他一些必要的字符组合,以防止像跨站脚本攻击(XSS)或者SQL注入攻击这样的安全问题呢?

7 个回答

13

防止XSS(跨站脚本攻击)的最好方法不是试图过滤所有内容,而是简单地进行HTML实体编码。比如,把<自动转换成&lt;。这个方法是理想的,前提是你不需要接受任何HTML输入(除了论坛或评论区这种用作标记的地方,通常不需要接受HTML);因为通过不同的编码方式,除了非常严格的白名单(比如只允许字母和数字a-z、A-Z、0-9),其他方式都可能会放过一些不安全的内容。

至于SQL注入,虽然有些人认为它不再可能,但如果你只是简单地拼接查询字符串,那还是会有SQL注入的风险。比如,如果你直接把一个传入的参数拼接到查询字符串中,就会导致SQL注入。保护自己免受这种攻击的最好方法同样不是过滤,而是严格使用参数化查询,绝对不要拼接用户输入。

这并不是说过滤就不重要了,它仍然是一个最佳实践。但在防止SQL注入和XSS方面,如果你严格使用参数化查询和HTML实体编码,你会更加安全。

23

编辑: 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 中会让我看到警告框,页面也会变空。

29

这里有一段代码,可以删除所有不在白名单上的标签,以及所有不在属性白名单上的标签属性(比如你不能使用 onclick)。

这段代码是对 http://www.djangosnippets.org/snippets/205/ 的修改版,增加了对属性值的正则表达式检查,以防止有人使用 href="javascript:..." 这样的写法,还有其他一些情况可以参考 http://ha.ckers.org/xss.html
(例如 <a href="ja&#x09;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 注入问题,所以这段代码基本上可以满足你的需求。

撰写回答