1

(我问这个问题(并回答它),以提供一些(希望有用的)信息,因为我无法使用搜索引擎轻易找到这个问题。但是,请随时回答它并添加有用的信息:-)。)

如何在 Python 中转义/引用 HTTP 标头?

和/或如何验证它们以确保它们不包含任何上下文转义值?

换句话说,我们如何处理 HTTP 标头, 以及处理 HTML 和 URL 的方法cgi.escapeurllib.quote方法(以及清理)?这可用于防止HTTP 标头注入和类似的攻击。

例如...

我们让用户提供一个应该重定向到的 URL。我们希望防止注入攻击(其中SQL 注入是众所周知的一种)。撇开(为了这个讨论)安全问题(关于秘密自动转发到用户可以选择的域中的 URL),如果我们决定使用Location:header重定向,我们如何转义用户提供的 URL 以防止 HTTP-header注入(或检测它是否包含对 HTTP 有危险的值)?

# on a "posix sh"-like command-line...
# ...(it contains a malicious HTTP value)
$ redirect_to 'http://example.com'"\r\n"'Set-Cookie: malicious=value'

现在,在我们实现该命令的 python 代码中redirect_to,我们希望像上面这样的输入要么被转义(使其无害),要么成为错误。我们怎么能这样做?

4

2 回答 2

1

不要逃避。只需停止处理(删除标头或整个请求)。

于 2013-10-28T07:22:18.650 回答
1

如果输入数据包含在标头字段参数中(例如标头的filename参数Content-Disposition),则可以对其进行编码(受这些规范email.utils.encode_rfc2231的约束,这些规范定义了rfc2231 编码的变体)。

如果没有包含头字段参数,则似乎无法使用此方法。在这种情况下,最安全的选择可能是不包括输入,正如Julian Reschke 所写的那样;但是,如果您坚持包含输入,则可能需要尝试以下方法之一:

(这可能是不安全的,因为HTTP 不是符合 MIME 的协议,因此除非使用标头(并且可能即使使用了标头?),这些方法可能MIME-Version无法正常用于 HTTP。)

单程...

要做到这一点,虽然它可能不是完全万无一失的(**编辑**:它*不是*万无一失的(当单独使用时);它接受 `\r\n\r\n`,它会终止标头并启动正文!因此需要处理`\r`和`\n`,除非前面有非`\r`/`\n`空格(如制表符或空格。)),是使用`email.header`模块。这是专门为rfc822 标头设计的(**edit**: 但是(似乎,因为电子邮件包曾经是几个单独的模块(示例))而不是 HTTP 标头!),所以似乎是这项工作的工具。这个 `Header` 类是用于编码 header *values*,而不是完整的 `Header-Name: value`,所以是这个工作的候选者(我们想要 vaidate 或逃避 value *only*)。

(提示:email模块中的许多工具在使用其他 MIME 格式(编辑:可能还有类似 MIME)的东西时也很方便;模块中的东西也很方便cgicgi.FieldStorage特别是用于 HTTP 格式的解析。)

但是,只有当输入看起来是恶意的(似乎包含另一个(嵌入的)标头)email.header时才会引发错误;但是,它似乎不会通过转义来处理无效输入(如果不是这样,请在评论中更正)。(该参数应该转义标头片段,返回有效输入,但是,它可能与用户代理(电子邮件、HTTP 等)没有那么好的兼容性;请参见此处编辑:<a href="https://stackoverflow .com/a/1361646/541412>许多HTTP用户代理支持(不一定是类的编码参数(除了rfc2231编码之外,它似乎使用了一些特定于MIME的编码),但是)rfc5987charsetcharsetemail.header.Header编码)。

例子:

import email.header
import re

def check_string_for_rfc822_header(s):
    wip_header_component = str(email.header.Header(s))
    if re.search(r'(\r?\n[\S\n\r]|\r[\S\r])', wip_header_component):
        raise Exception
    else:
        return wip_header_component

# testing...
>>> check_string_for_rfc822_header("aaa")
"aaa"
>>> check_string_for_rfc822_header("a\r\nb")
"a\r\nb"
>>> check_string_for_rfc822_header("a\r\nb: c")
<error>

另一种方式...

要做到这一点,似乎只是简单地删除 `\r` 和 `\n` 字符(但是,每个字符都是分开的;不要只删除出现的完整字符串 `\r\n`,因为这仍然会使这些未转义单独发生,并且许多(大多数?)HTTP utils 将分别接受它们中的每一个!)。类似地,我们可以通过替换 `\r\n`、`\r` 和 `\n` 来转义标头,并在它们的前面加上空格(这是转义标头的方法;请参阅标准)。

但是,这种方法没有考虑到标准的细节(例如,rfc822 标头必须是 ACSII),这些标准本身可能是可利用的。

例子:

def remove_linebreakers(s):
    return s.replace("\n", "").replace("\r", "")

# or...
import re

def remove_linebreakers(s):
    re.sub(r'[\n\r]', '', s)


# testing...
>>> remove_linebreakers("aaa")
"aaa"
>>> remove_linebreakers("a\r\nb")
"ab"
>>> remove_linebreakers("a\r\nb: c")
"ab: c"

总之...

第一种方法似乎更好,但仅用于验证(不用于转义),除非它是参数值,在这种情况下使用`email.utils.encode_rfc2231`对其进行转义。

例子:

# if we are not working with a header param value, the following...
# ...raises email.errors.HeaderParseError if input is poisonous when in a header
wip_header_component = str(email.header.Header('<input>'))
header_component = (raise_error() if re.search(r'(\r?\n[\S\n\r]|\r[\S\r])', wip_header_component) else wip_header_component)
# ...or if we *are* working with a header param value...
email.utils.encode_rfc2231('<input>', 'UTF-8')
于 2013-10-28T07:06:45.137 回答