如何停止HTTP(和rfc822,电子邮件)标头注入?

时间:2013-10-28 06:32:16

标签: python http-headers code-injection email-headers rfc822

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

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

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

换句话说,我们如何处理HTTP标头,cgi.escapeurllib.quote方法(以及消毒)对HTML和网址做了什么?这可以用来防范HTTP header injection和类似的攻击。<​​/ p>

例如......

我们让用户提供应该重定向的URL。我们希望防止注入攻击(其中SQL injection是众所周知的)。保留(为此讨论)安全问题(关于秘密自动转发到用户可以选择的域中的URL),如果我们决定重定向using the 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'

现在,在我们实现redirect_to命令的python代码中,我们想要像上面那样输入要么被转义(使它无害),要么是一个错误。我们怎么能这样做?

2 个答案:

答案 0 :(得分:1)

如果输入数据包含在标题字段参数中(例如filename parameter of the Content-Disposition header),则可以使用email.utils.encode_rfc2231对其进行编码(由{{3}约束},用于定义these specifications)的变体。

如果包含头字段参数,则似乎无法使用此方法。在这种情况下,最安全的赌注可能是不包括输入,如rfc2231 encoding;但是,如果您坚持要包含输入,则可能需要尝试以下方法之一:

(可能不安全,因为Julian Reschke wrote,所以除非HTTP is not a MIME-compliant protocol(甚至可能使用它?),HTTP的这些方式可能无法正常工作 。)

单程......

要做到这一点,虽然它可能不是完全万无一失的(编辑:它万无一失(当自己使用时);它接受\r\n\r\n,它会终止标题并启动正文!因此需要处理\r\n,除非前面有非\r / \n空格(如制表符或空格)。) ,是使用the MIME-Version header is used模块。这是专为email.header设计的(编辑:但是(貌似,因为电子邮件包曾经是几个单独的模块(rfc822 headers))example),所以似乎是工作的工具。此Header类用于编码标头,而不是完整的Header-Name: value,因此是此作业的候选者(我们想要在其中放弃或转义值)。

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

但是,如果输入似乎恶意(似乎包含另一个(嵌入式)标头),not for HTTP headers!只会引发错误;但是,它似乎不会通过转义来处理无效的输入(如果不是这样,请在评论中更正)。 (charset参数应该转义标题片段,返回有效输入,但是,它可能与用户代理(电子邮件,HTTP等)没有很好的兼容性;请参阅{{ 3}}(编辑:许多HTTP用户代理支持(不一定是charset类编码的email.header.Header参数(除了使用某些特定于MIME的编码之外) rfc2231编码),但是email.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>

另一种方式......

这样做,似乎只是简单地here(但每个单独分开;不要只删除完整字符串\r\n,因为当单独出现时,这仍然会保留未转义状态,并且许多(大多数?)HTTP工具将分别接受它们中的每一个!)。同样,我们可以通过替换\r\n\r\n来转义标头,它们自己以空格为前缀(这是转义标头的方式;请参阅rfc5987)。

但是,此方法未考虑标准的详细信息(例如,rfc822标题remove \r and \n characters),这些标准可能会自行利用。

示例:

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"

总结......

第一种方式似乎更好,但仅用于验证(不用于转义),除非它是参数值,在这种情况下使用the standard转义它。

示例:

# 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')

答案 1 :(得分:1)

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