Werkzeug: Werkzeug salah menangani header multiline

Dibuat pada 10 Mar 2017  ·  8Komentar  ·  Sumber: pallets/werkzeug

Menurut RFC 2616 :

Nilai bidang header HTTP/1.1 dapat dilipat menjadi beberapa baris jika baris lanjutan dimulai dengan spasi atau tab horizontal. Semua ruang putih linier, termasuk lipat, memiliki semantik yang sama dengan SP. Penerima MUNGKIN mengganti spasi putih linier dengan SP tunggal sebelum menafsirkan nilai bidang atau meneruskan pesan ke hilir.

Namun, werkzeug tidak menerima nilai header dengan baris baru, bahkan jika mereka mematuhi konvensi ini.

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

Juga, pembatasan ini diterapkan secara tidak konsisten.

>>> werkzeug.Headers([('foo', 'bar\n baz')])
Headers([('foo', 'bar\n baz')])

Saya mengalami masalah ini ketika mencoba menulis kasus uji yang berkaitan dengan penerusan nginx dari sertifikat klien melalui header, jadi ada kasus penggunaan nyata untuk mendukung ini dengan benar.

bug server

Komentar yang paling membantu

@davidism Seperti yang saya sebutkan di komentar sebelumnya, sebenarnya ada dua bug di sini, tidak ada yang diperbaiki di cabang master saat ini.


Bug pertama melibatkan bagaimana server pengembangan werkzeug menangani header yang dibungkus baris. Itu dapat direproduksi dengan kode server berikut, yang mencetak nilai header 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)

Kami kemudian dapat mengirimkan permintaan dengan tajuk yang mencakup beberapa baris:

GET / HTTP/1.1
Host: localhost:8080
Connection: close
X-Example: foo
 bar

Keluaran server yang diharapkan:
Nilai header digabungkan menjadi satu baris

'foo bar'

Output server aktual (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
----------------------------------------

Output server aktual (Python 3):
Nilai header berisi karakter baris baru yang tidak diizinkan oleh spesifikasi WSGI

'foo\r\n bar'

Bug kedua berkaitan dengan bagaimana objek Headers menangani nilai baris baru.

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

Hasil yang diharapkan:
Kedua operasi harus memiliki hasil yang sama, apakah ValueError atau sukses.

Hasil sebenarnya:
Headers konstruktor memungkinkan baris baru sementara Headers.add() memunculkan ValueError .

Semua 8 komentar

Header HTTP tidak mengizinkan baris baru. Bagian yang Anda kutip berbicara tentang melipat, yang harus menghapus baris baru dari nilai yang tidak dilipat. Ini:

"""foo
     bar"""

harus dibuka menjadi sesuatu seperti ini:

"foo bar"

Selain itu, Werkzeug tidak mem-parsing HTTP pada level ini, ini adalah tugas dari server WSGI. Satu-satunya alasan menolak baris baru saat mem-parsing permintaan adalah untuk menangkap masalah keamanan.

Tiket ini dimotivasi oleh perilaku nyata Flask dalam mode pengembangan di belakang sertifikat klien penerusan proxy nginx. Dengan pengaturan itu, saya mengamati baris baru di header yang diteruskan ke aplikasi. Tetapi ketika saya mencoba mereplikasi ini dalam pengujian unit, saya mendapatkan ValueError saat membuat header permintaan.

Saya melakukan sedikit penelitian lebih lanjut tentang masalah ini dan menemukan yang berikut:

  • Spesifikasi HTTP ( RFC 2616 ) menyatakan bahwa baris baru di header MUNGKIN diganti dengan satu spasi, bukan karena dijamin (lihat kutipan di atas).

  • Spesifikasi CGI ( RFC 3875 ), yang diperluas WSGI, memerlukan penggantian baris baru di header permintaan:

    Demikian pula, bidang header yang mencakup beberapa baris HARUS digabung menjadi satu baris.

  • Spesifikasi WSGI ( PEP 333 ) juga melarang baris baru di header respons:

    Setiap header_value tidak boleh menyertakan karakter kontrol apa pun, termasuk carriage return atau linefeeds, baik yang disematkan atau di akhir.

Jadi ini berarti ada dua bug di sini:

  • Server pengembangan werkzeug tidak menormalkan string terlipat dengan benar di header permintaan.

  • Objek Headers tidak konsisten tentang menerima baris baru dalam nilai header. Baris baru diterima di konstruktor, tetapi tidak dalam metode add() . Mungkin lebih baik objek Headers tetap permisif dan melakukan validasi ketika benar-benar membangun respons WSGI di BaseResponse . Dengan begitu melarang baris baru di konstruktor tidak akan merusak kompatibilitas dengan server WSGI yang mungkin tidak sesuai (seperti server pengembangan milik werkzeug sendiri).

Cukup adil. Ada juga #1070 yang berperan dalam hal ini.

Cukup yakin ini telah diperbaiki dengan perbaikan untuk #1070. Jika tidak, beri tahu saya dengan contoh yang dapat direproduksi.

@davidism Seperti yang saya sebutkan di komentar sebelumnya, sebenarnya ada dua bug di sini, tidak ada yang diperbaiki di cabang master saat ini.


Bug pertama melibatkan bagaimana server pengembangan werkzeug menangani header yang dibungkus baris. Itu dapat direproduksi dengan kode server berikut, yang mencetak nilai header 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)

Kami kemudian dapat mengirimkan permintaan dengan tajuk yang mencakup beberapa baris:

GET / HTTP/1.1
Host: localhost:8080
Connection: close
X-Example: foo
 bar

Keluaran server yang diharapkan:
Nilai header digabungkan menjadi satu baris

'foo bar'

Output server aktual (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
----------------------------------------

Output server aktual (Python 3):
Nilai header berisi karakter baris baru yang tidak diizinkan oleh spesifikasi WSGI

'foo\r\n bar'

Bug kedua berkaitan dengan bagaimana objek Headers menangani nilai baris baru.

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

Hasil yang diharapkan:
Kedua operasi harus memiliki hasil yang sama, apakah ValueError atau sukses.

Hasil sebenarnya:
Headers konstruktor memungkinkan baris baru sementara Headers.add() memunculkan ValueError .

Saya melihat ValueError baru-baru ini di salah satu proyek kami (Python 2.7 dengan Flask) yang kebetulan berada di belakang proxy nginx. Saya akhirnya kembali ke 0.14.1 (mengganti versi yang dibundel dengan Flask ) dan kesalahan saya hilang. Pikir saya akan menambahkan karena tampaknya cabang 0.15.x memperkenalkan masalah ini (atau berpotensi menciptakan masalah baru dengan cara menangani header permintaan).

------MEMPERBARUI-------:

Saya melacak tajuk apa yang kami lewati dan salah satunya adalah sertifikat multi-baris dalam format pem yaitu:

SSL_CLIENT_CERT: -----BEGIN CERTIFICATE-----
    MIIFHzCCAwegAwIBAgICEDgwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCdXMx
    GDAWBgNVBAoMD3Uucy4gZ292ZXJubWVudDEPMA0GA1UECwwGcGVvcGxlMQwwCgYD
    VQQLDANkYWUxEDAOBgNVBAsMB2NoaW1lcmExEDAOBgNVBAMMB0ludGVyQ0EwHhcN
    MTcwODMxMTUwMzEwWhcNMjcwODI5MTUwMzEwWjBwMQswCQYDVQQGEwJVUzEYMBYG
    A1UECgwPVS5TLiBHb3Zlcm5tZW50MRAwDgYDVQQLDAdjaGltZXJhMQwwCgYDVQQL
    ....  
    -----END CERTIFICATE-----

Server nginx kami dikonfigurasi seperti ini:

proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;

Kita mungkin harus menggunakan $ssl_client_escaped_cert daripada $ssl_client_cert (karena ini sudah usang). Tidak yakin apakah perubahan itu akan memperbaiki masalah penguraian header.

Berharap ini bisa membantu orang lain yang mengalami masalah ini. Tampaknya 0.15.1 tidak menangani header multi-baris dengan benar seperti sertifikat PEM saat ini dengan Python 2.7.

Ini adalah masalah 2,7 lagi yang disebabkan oleh pemrosesan header dari server pengembangan. Saya menambahkan kemampuan untuk memproses pelipatan header ke kode kompatibilitas 2.7 untuk header permintaan.

Dikonfirmasi bahwa bug server pengembangan sekarang telah diperbaiki di Python 2 dan 3. Masalah kedua dengan objek Headers masih ada. Dibuka #1608.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat