使用Python进行用户输入的净化处理

64

如何在基于Python的Web应用程序中对用户输入进行最佳清理?是否有单个函数可用于删除HTML字符和任何其他必要的字符组合,以防止XSS或SQL注入攻击?


10
尝试通过净化用户输入来修复SQL注入是不正确的做法!只要正确使用数据库API,就不会发生SQL注入的可能性。 - John La Rooy
4
如果正确使用数据库API,就不会有SQL注入的风险。你所说的"正确使用"是指使用参数化查询吗?这样做能够100%覆盖吗? - user
2
@buffer,我知道你的评论已经有些旧了,但是如果你想让除了原帖作者以外的其他人看到你的评论,你需要在他们的名字前加上 @ 符号。 - user1717828
7个回答

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注入问题,因此这应该基本上可以满足您的需求。


2
我点赞了这个,但现在我不太确定了。我认为这并不能保护IE用户免受src="vbscript:msgbox('xss')"攻击的影响。 - Gareth Simpson
你可以很容易地通过另一个VBScript的正则表达式来添加它,就像JavaScript的那个一样。 - tghw
1
@tghw,这里的VBScript示例说明了为什么白名单解决方案通常比黑名单解决方案更可取。你怎么确定你需要的所有东西都在黑名单上?对于黑名单来说,下周可能会出现一个新的浏览器,因为它支持新类型的脚本标签而导致漏洞。 - John La Rooy
2
@gnibbler 我同意,大部分都是白名单解决方案,但对于 href 和 src,真的很难轻松地列出白名单。我能想到的唯一选择就是通过传入页面 URL 并遍历每个链接和图像来确定基于页面 URL 的绝对 URL,从而使所有 URL 都成为绝对路径。我越想越觉得这很容易。我会将其添加在上面。 - tghw
这太完美了。我将用它来清理我的小博客上的评论。 :) -- 谢谢。 - PKKid
显示剩余2条评论

23

编辑: bleach是一个围绕html5lib的包装器,使其更易于用作基于白名单的清理器。

html5lib带有基于白名单的HTML清理器-您可以轻松地对其进行子类化,以限制用户在您的站点上允许使用的标签和属性,甚至尝试清理CSS,如果您允许使用style属性。

以下是我在我的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转换后,看起来它们都能够正常运行。
然而,Stackoverflow目前使用的WMD编辑器组件是个问题——事实上,我不得不禁用JavaScript才能够测试XSS防范清单中的攻击方式,因为将它们全部粘贴到WMD中最终导致弹出警告框并且页面被清空。

2023年更新:bleach已被弃用。新的推荐似乎是nh3 - Che

13
最好的防止XSS的方法不是试图过滤所有内容,而是简单地进行HTML实体编码。例如,自动将<转换为&lt;。这是理想的解决方案,假设您不需要接受任何HTML输入(除了用作标记的论坛/评论区之外,在其他地方需要接受HTML应该相当少见);由于有太多通过替代编码的排列组合,因此除了超严格的白名单(例如a-z、A-Z、0-9)之外,任何东西都会被允许通过。
与其他观点相反,SQL注入仍然可能存在,如果你只是构建一个查询字符串。例如,如果你只是将一个传入参数连接到一个查询字符串上,那么你就会有SQL注入。最好的保护方法也不是过滤,而是宗教般地使用参数化查询,并永远不要连接用户输入。
这并不是说过滤不再是最佳实践,但就SQL注入和XSS而言,如果您宗教般地使用参数化查询和HTML实体编码,您将受到更大的保护。

1
这在许多情况下都不正确。请参阅OWASP有关“为什么我不能只使用HTML实体编码不受信任的数据?”的注释:https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet - Purrell

6

Jeff Atwood在Stack Overflow博客中描述了StackOverflow.com如何对用户输入进行消毒(用非特定语言的术语)。https://blog.stackoverflow.com/2008/06/safe-html-and-xss/

然而,正如Justin所指出的,如果您使用Django模板或类似的东西,则它们可能会自动处理HTML输出的消毒。

SQL注入也不应该是一个问题。Python的所有数据库库(MySQLdb、cx_Oracle等)都始终对您传递的参数进行消毒。这些库被所有Python对象关系映射器(如Django模型)使用,因此您在那里也不需要担心消毒问题。


4

我已经不再从事Web开发了,但是当我还在从事时,我的做法通常是这样的:

如果不需要解析,我通常只是将数据进行转义以避免存储时干扰数据库,并将从数据库读取的所有内容进行转义以避免在显示时干扰HTML(在Python中使用cgi.escape())。

有可能,如果有人尝试输入HTML字符或其他内容,他们实际上希望将其显示为文本。如果他们没有这样的意图,那就很难处理 :)

总之,始终要转义可能影响当前数据目标的内容。

如果需要进行一些解析(标记或其他),我通常会尝试将该语言保持在与HTML不重叠的集合中,以便在存储时适当地进行转义(在验证语法错误后),并在显示时将其解析为HTML,而不必担心用户输入的数据会干扰您的HTML。

另请参阅Escaping HTML


0

为了清理要存储到数据库中的字符串输入(例如客户姓名),您需要对其进行转义或直接删除其中的任何引号(',")。这有效地防止了经典的SQL注入攻击,因为如果您正在从用户传递的字符串组装SQL查询,则可能会发生此类攻击。

例如(如果完全删除引号是可以接受的):

datasetName = datasetName.replace("'","").replace('"',"")

1
额...不...我仍然不会这样做。对于所有数据项,请使用参数化查询。对于非数据(动态构建的查询),您应该确实使用白名单。pg_catalog.pg_user不包含引号,但您可能不希望在生成的查询中出现它。相反,像这样做:datasetName = datasetName if datasetName in DATASETNAME_WHITELIST else sulk() - SingleNegationElimination

0

如果您正在使用像django这样的框架,该框架可以使用标准过滤器轻松为您完成此操作。实际上,我相信django会自动执行此操作,除非您告诉它不要这样做。

否则,在接受表单输入之前,我建议使用某种正则表达式验证。我认为没有解决您问题的万能方法,但是使用re模块,您应该能够构建所需内容。


我认为Django的内置功能无法防止XSS攻击,根据这里的文档:https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#striptags。我建议使用`bleach`。 - AthulMuralidhar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接