Werkzeug: Werkzeugが複数行のヘッダーを誤って処理する

作成日 2017年03月10日  ·  8コメント  ·  ソース: pallets/werkzeug

RFC 2616によると:

継続行がスペースまたは水平タブで始まる場合、HTTP /1.1ヘッダーフィールド値を複数の行に折りたたむことができます。 折りたたみを含むすべての線形空白は、SPと同じセマンティクスを持っています。 受信者は、フィールド値を解釈したり、メッセージをダウンストリームに転送したりする前に、線形空白を単一のSPに置き換えることができます(MAY)。

ただし、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転送に関連するテストケースを書き込もうとしたときにこの問題が発生したため、これを適切にサポートするための実際のユースケースがあります。

bug server

最も参考になるコメント

@davidism前のコメントで述べたように、ここには実際には2つのバグがあり、どちらも現在のマスターブランチでは修正されていません。


最初のバグは、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

予想されるサーバー出力:
ヘッダー値は1行にマージされます

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

2番目のバグは、 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ます。

全てのコメント8件

HTTPヘッダーは改行を許可しません。 引用しているセクションでは、折り畳みについて説明しています。折り畳みは、展開された値から改行を削除する必要があります。 この:

"""foo
     bar"""

次のようなものに展開されるはずです:

"foo bar"

それとは別に、WerkzeugはこのレベルでHTTPを解析しません。これは、WSGIサーバーの仕事です。 リクエストを解析するときに改行を拒否する唯一の理由は、セキュリティの問題をキャッチすることです。

このチケットは、nginxプロキシ転送クライアント証明書の背後にある開発モードでのFlaskの実際の動作によって動機付けられました。 その設定で、アプリケーションに渡されるヘッダーの改行を観察しました。 しかし、単体テストでこれを複製しようとすると、リクエストヘッダーを作成するときに上記のValueErrorが得られました。

この問題についてもう少し調査したところ、次のことがわかりました。

  • HTTP仕様( RFC 2616 )は、ヘッダーの改行は単一のスペースに置き換えることができると述べていますが、そうなることが保証されているわけではありません(上記の引用を参照)。

  • WSGIが拡張するCGI仕様( RFC 3875 )では、要求ヘッダーの改行を置き換える必要があります。

    同様に、複数行にまたがるヘッダーフィールドは、1行にマージする必要があります。

  • WSGI仕様( PEP 333 )では、応答ヘッダーの改行も禁止されています。

    header_valueは、改行や改行などの制御文字を埋め込みまたは最後に含めることはできません。

つまり、ここには2つのバグがあるということです。

  • werkzeug開発サーバーは、リクエストヘッダーの折りたたまれた文字列を正しく正規化しません。

  • Headersオブジェクトは、ヘッダー値の改行の受け入れに関して一貫性がありません。 改行はコンストラクターで受け入れられますが、 add()メソッドでは受け入れられません。 BaseResponse WSGI応答を実際に構築するときは、 Headersオブジェクトが許容範囲を維持し、検証を実行する方がおそらく良いでしょう。 そうすれば、コンストラクターで改行を禁止しても、おそらく不適合なWSGIサーバー(werkzeug自身の開発サーバーなど)との互換性が損なわれることはありません。

けっこうだ。 これに関係する#1070もあります。

#1070の修正でこれが修正されたことは間違いありません。 そうでない場合は、再現可能な例でお知らせください。

@davidism前のコメントで述べたように、ここには実際には2つのバグがあり、どちらも現在のマスターブランチでは修正されていません。


最初のバグは、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

予想されるサーバー出力:
ヘッダー値は1行にマージされます

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

2番目のバグは、 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ます。

最近、私たちのプロジェクトの1つ(Python 2.7とFlask)でValueErrorされましたが、これはnginxプロキシの背後にあります。 最終的に0.14.1戻り( Flaskバンドルされているバージョンをオーバーライドします)、エラーはなくなりました。 0.15.xブランチがこの問題を引き起こした(またはリクエストヘッダーの処理方法に新しい問題を引き起こした可能性がある)ため、追加したいと思いました。

- - - 更新 - - - -:

渡したヘッダーを追跡しました。そのうちの1つは、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 (これはとにかく推奨されませんので)。 ただし、その変更によってヘッダー解析の問題が修正されるかどうかはわかりません。

これがこの問題に直面している他の人に役立つことを願っています。 現時点では、Python 2.7では0.15.1がPEM証明書のような複数行のヘッダーを適切に処理していないようです。

これも、開発サーバーのヘッダー処理が原因で発生する2.7の問題です。 ヘッダーの折りたたみを処理する機能を、リクエストヘッダーの2.7互換性コードに追加しています。

開発サーバーのバグがPython2と3の両方で修正されたことを確認しました。 Headersオブジェクトに関する2番目の問題は引き続き存在します。 #1608を開きました。

このページは役に立ちましたか?
0 / 5 - 0 評価