如何停止 HTTP(和 rfc822、电子邮件)标头注入?
Posted
技术标签:
【中文标题】如何停止 HTTP(和 rfc822、电子邮件)标头注入?【英文标题】:How to stop HTTP (and rfc822, email) header injection? 【发布时间】:2013-11-06 20:21:29 【问题描述】:(我在问这个问题(并回答它),以提供一些(希望有用的)信息,因为我无法使用搜索引擎轻松找到它。但是,请随时回答并添加有用的信息 :-) .)
如何在 Python 中转义/引用 HTTP 标头?
和/或如何验证它们以确保它们不包含任何上下文转义值?
换句话说,我们如何处理 HTTP 标头,cgi.escape
和 urllib.quote
方法(和清理)对 html 和 URL 做了什么?这可以用来防范HTTP header injection 和类似的攻击。
例如...
我们让用户提供一个应该重定向到的 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 代码中,我们希望像上面这样的输入被转义(使其无害)或成为错误。我们该怎么做?
【问题讨论】:
【参考方案1】:不要逃避。只需停止处理(删除标头或整个请求)。
【讨论】:
我认为我没有在问题中正确解释自己;我修改了它(和它的标题),试图解释更多。 我仍然不会生成重定向。只需返回 400。 是的,我想这更有意义。我更新了我的答案,使用info I gleaned 处理文件名,这是我目前的用例,后来我意识到,some of 与这些主题有关的标准,were authored 由你 :-)【参考方案2】:如果输入数据包含在标头字段参数中(例如filename
parameter of the Content-Disposition
header),则可以使用email.utils.encode_rfc2231
对其进行编码(受these specifications的约束,它定义了一个rfc2231 encoding 的变体)。
如果是不被包含一个头域参数,那么这个方法好像不能用。在这种情况下,最安全的选择可能是不包括输入,如Julian Reschke wrote;但是,如果您坚持包含输入,则可能需要尝试以下方法之一:
(这可能是不安全的,因为HTTP is not a MIME-compliant protocol,所以除非the MIME-Version
header is used(甚至可能使用它?),这些方式可能无法正常工作用于HTTP。)
一种方式...
要做到这一点,虽然它可能不是完全万无一失的(**编辑**:它*不是*万无一失的(当单独使用时);它接受 `\r\n\r\n`,它会终止标头并启动正文!因此需要处理`\r`和`\n`,除非前面有非`\r`/`\n`空格(如制表符或空格。)),是使用`email.header`模块。这是专门为rfc822 headers设计的(**编辑**:但是(似乎,因为电子邮件包曾经是几个单独的模块(example))not for HTTP headers!),所以似乎是这项工作的工具.这个 `Header` 类是用来编码 header *values* 的,而不是完整的 `Header-Name: value`,所以是这个工作的候选者(我们想要 vaidate 或逃避 value *only*)。(提示:email
模块中的许多工具在处理其他 MIME 格式(编辑:可能还有类似 MIME)的东西时也很方便;在cgi
模块,cgi.FieldStorage
特别是用于 HTTP 表单解析。)
然而,email.header
只会在输入看起来有恶意(似乎包含另一个(嵌入的)标头)时引发错误;但是,它似乎不会通过转义来处理 invalid 输入(如果不是这样,请在 cmets 中更正此问题)。 (charset
参数应该转义 header-fragment,返回 valid 输入,但是,它可能与用户代理(电子邮件、HTTP 等)没有那么好的兼容性;参见 here ( 编辑:rfc5987 编码)。
例子:
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>
另一种方式...
要做到这一点,似乎只是简单地remove `\r` and `\n` characters (但是,每个都是单独的;不要只删除完整字符串 `\r\n` 的出现,因为这仍然会使这些单独出现时未转义,并且许多(大多数? ) HTTP utils 将分别接受它们中的每一个!)。类似地,我们可以通过替换 `\r\n`、`\r` 和 `\n` 来转义标头,并在它们的前面加上空格(这是转义标头的方法;请参阅 the standard)。但是,这种方法没有考虑到标准的细节(例如,rfc822 标头must be 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')
【讨论】:
以上是关于如何停止 HTTP(和 rfc822、电子邮件)标头注入?的主要内容,如果未能解决你的问题,请参考以下文章
尝试使用 gmail api 发送电子邮件接收“原始”RFC822 有效负载消息字符串
使用 Google API 发送邮件时出错 - “'原始' RFC822 有效负载消息字符串或通过 /upload/* URL 上传消息”