根据RFC 2616 :
如果续行以空格或水平制表符开头,则 HTTP/1.1 标头字段值可以折叠成多行。 所有线性空白,包括折叠,都具有与 SP 相同的语义。 在解释字段值或向下游转发消息之前,接收者可以用单个 SP 替换任何线性空白。
但是,werkzeug 不接受带有换行符的标头值,即使它们遵守此约定。
>>> import werkzeug
>>> werkzeug.Headers().add('foo', 'bar\n baz')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".../venv/local/lib/python2.7/site-packages/werkzeug/datastructures.py", line 1136, in add
self._validate_value(_value)
File ".../venv/local/lib/python2.7/site-packages/werkzeug/datastructures.py", line 1143, in _validate_value
raise ValueError('Detected newline in header value. This is '
ValueError: Detected newline in header value. This is a potential security problem
此外,此限制的应用不一致。
>>> werkzeug.Headers([('foo', 'bar\n baz')])
Headers([('foo', 'bar\n baz')])
我在尝试编写与通过标头转发客户端证书的 nginx 相关的测试用例时遇到了这个问题,因此有一个真正的用例来正确支持它。
HTTP头不允许换行。 您引用的部分讨论了折叠,它应该从展开的值中删除换行符。 这:
"""foo
bar"""
应该展开成这样:
"foo bar"
除此之外,Werkzeug 不在此级别解析 HTTP,这是 WSGI 服务器的工作。 它在解析请求时拒绝换行的唯一原因是为了捕捉安全问题。
这张票的动机是 Flask 在开发模式下在 nginx 代理转发客户端证书背后的真实行为。 通过该设置,我观察到传递给应用程序的标头中的换行符。 但是当我试图在单元测试中复制它时,我在构建请求标头时得到了上面的ValueError
。
我对这个问题做了更多的研究,发现以下内容:
HTTP 规范 ( RFC 2616 ) 指出,标头中的换行符可以替换为单个空格,而不是保证它们是(参见上面的引用)。
WSGI 扩展的 CGI 规范 ( RFC 3875 ) 要求替换请求标头中的换行符:
类似地,跨越多行的标题字段必须合并为一行。
WSGI 规范 ( PEP 333 ) 也禁止在响应头中使用换行符:
每个
header_value
不得包含任何控制字符,包括回车符或换行符,无论是嵌入的还是末尾的。
所以这意味着这里有两个错误:
werkzeug 开发服务器未正确规范化请求标头中的折叠字符串。
Headers
对象在接受标头值中的换行符方面不一致。 构造函数中接受换行符,但add()
方法中不接受换行符。 在BaseResponse
实际构造 WSGI 响应时, Headers
对象保持宽松并执行验证可能更好。 这样在构造函数中禁止换行不会破坏与可能不符合标准的 WSGI 服务器(例如 werkzeug 自己的开发服务器)的兼容性。
很公平。 还有#1070 也参与其中。
很确定这已通过 #1070 的修复程序修复。 如果没有,请告诉我一个可重现的例子。
@davidism正如我在之前的评论中提到的,这里实际上有两个错误,当前的主分支上都没有修复。
第一个错误涉及 werkzeug 开发服务器如何处理换行的标头。 可以使用以下服务器代码重现它,该代码打印X-Example
标头的值:
from werkzeug.serving import run_simple
from werkzeug.wrappers import Request, Response
def app(environ, start_response):
request = Request(environ)
print(repr(request.headers.get('X-Example')))
response = Response(status=204)
return response(environ, start_response)
run_simple('localhost', 8080, app)
然后我们可以向它发送一个包含多行标头的请求:
GET / HTTP/1.1
Host: localhost:8080
Connection: close
X-Example: foo
bar
预期的服务器输出:
标题值合并为一行
'foo bar'
实际服务器输出(Python 2):
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 57361)
Traceback (most recent call last):
File "/usr/lib/python2.7/SocketServer.py", line 295, in _handle_request_noblock
self.process_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 321, in process_request
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 334, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.7/SocketServer.py", line 649, in __init__
self.handle()
File "/home/.../venv/local/lib/python2.7/site-packages/werkzeug/serving.py", line 320, in handle
rv = BaseHTTPRequestHandler.handle(self)
File "/usr/lib/python2.7/BaseHTTPServer.py", line 340, in handle
self.handle_one_request()
File "/home/.../venv/local/lib/python2.7/site-packages/werkzeug/serving.py", line 355, in handle_one_request
return self.run_wsgi()
File "/home/.../venv/local/lib/python2.7/site-packages/werkzeug/serving.py", line 238, in run_wsgi
self.environ = environ = self.make_environ()
File "/home/.../venv/local/lib/python2.7/site-packages/werkzeug/serving.py", line 217, in make_environ
for key, value in self.get_header_items():
File "/home/.../venv/local/lib/python2.7/site-packages/werkzeug/serving.py", line 441, in get_header_items
key, value = header[0:-2].split(":", 1)
ValueError: need more than 1 value to unpack
----------------------------------------
实际服务器输出(Python 3):
标头值包含 WSGI 规范不允许的换行符
'foo\r\n bar'
第二个错误与Headers
对象如何处理换行符有关。
>>> from werkzeug import Headers
>>> h1 = Headers([('X-Example', 'foo\r\n bar')])
>>> h2 = Headers()
>>> h2.add('X-Example', 'foo\r\n bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/.../venv3/lib/python3.6/site-packages/werkzeug/datastructures.py", line 1166, in add
self._validate_value(_value)
File "/home/.../venv3/lib/python3.6/site-packages/werkzeug/datastructures.py", line 1173, in _validate_value
raise ValueError('Detected newline in header value. This is '
ValueError: Detected newline in header value. This is a potential security problem
预期结果:
无论是ValueError
还是成功,这两个操作都应该具有相同的结果。
实际结果:
Headers
构造函数允许换行,而Headers.add()
会引发ValueError
。
我最近在我们的一个项目(带有 Flask 的 Python 2.7)中看到了ValueError
,它恰好位于 nginx 代理后面。 我最终恢复到0.14.1
(覆盖与Flask
捆绑在一起的版本)并且我的错误消失了。 我想我会添加,因为似乎0.15.x
分支引入了这个问题(或者可能会在处理请求标头方面产生一个新问题)。
- - - 更新 - - - -:
我追踪了我们传递的头文件,其中之一是 pem 格式的多行证书,即:
SSL_CLIENT_CERT: -----BEGIN CERTIFICATE-----
MIIFHzCCAwegAwIBAgICEDgwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCdXMx
GDAWBgNVBAoMD3Uucy4gZ292ZXJubWVudDEPMA0GA1UECwwGcGVvcGxlMQwwCgYD
VQQLDANkYWUxEDAOBgNVBAsMB2NoaW1lcmExEDAOBgNVBAMMB0ludGVyQ0EwHhcN
MTcwODMxMTUwMzEwWhcNMjcwODI5MTUwMzEwWjBwMQswCQYDVQQGEwJVUzEYMBYG
A1UECgwPVS5TLiBHb3Zlcm5tZW50MRAwDgYDVQQLDAdjaGltZXJhMQwwCgYDVQQL
....
-----END CERTIFICATE-----
我们的 nginx 服务器配置如下:
proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;
我们可能应该使用$ssl_client_escaped_cert
而不是$ssl_client_cert
(因为无论如何都不推荐使用)。 不确定该更改是否会解决标头解析问题。
希望这可以帮助遇到此问题的任何其他人。 似乎0.15.1
目前无法使用 Python 2.7 正确处理多行标题,如 PEM 证书。
这又是2.7的问题,是开发服务器的header处理引起的。 我正在为请求标头的 2.7 兼容性代码添加处理标头折叠的功能。
确认开发服务器错误现在在 Python 2 和 3 中都已修复。 Headers
对象的第二个问题仍然存在。 打开#1608。
最有用的评论
@davidism正如我在之前的评论中提到的,这里实际上有两个错误,当前的主分支上都没有修复。
第一个错误涉及 werkzeug 开发服务器如何处理换行的标头。 可以使用以下服务器代码重现它,该代码打印
X-Example
标头的值:然后我们可以向它发送一个包含多行标头的请求:
预期的服务器输出:
标题值合并为一行
实际服务器输出(Python 2):
实际服务器输出(Python 3):
标头值包含 WSGI 规范不允许的换行符
第二个错误与
Headers
对象如何处理换行符有关。预期结果:
无论是
ValueError
还是成功,这两个操作都应该具有相同的结果。实际结果:
Headers
构造函数允许换行,而Headers.add()
会引发ValueError
。