Werkzeug: Werkzeug verarbeitet mehrzeilige Kopfzeilen falsch

Erstellt am 10. März 2017  ·  8Kommentare  ·  Quelle: pallets/werkzeug

Gemäß RFC 2616 :

HTTP/1.1-Header-Feldwerte können auf mehrere Zeilen gefaltet werden, wenn die Fortsetzungszeile mit einem Leerzeichen oder einem horizontalen Tabulator beginnt. Der gesamte lineare Leerraum, einschließlich der Faltung, hat dieselbe Semantik wie SP. Ein Empfänger KANN jeden linearen Leerraum durch einen einzelnen SP ersetzen, bevor er den Feldwert interpretiert oder die Nachricht stromabwärts weiterleitet.

werkzeug akzeptiert jedoch keine Header-Werte mit Zeilenumbrüchen, selbst wenn diese dieser Konvention entsprechen.

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

Außerdem wird diese Einschränkung uneinheitlich angewendet.

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

Ich bin auf dieses Problem gestoßen, als ich versucht habe, Testfälle in Bezug auf die nginx-Weiterleitung von Client-Zertifikaten über Header zu schreiben, daher gibt es einen echten Anwendungsfall für die ordnungsgemäße Unterstützung dieser.

bug server

Hilfreichster Kommentar

@davidism Wie ich in einem früheren Kommentar erwähnt habe, gibt es hier tatsächlich zwei Fehler, von denen keiner im aktuellen Master-Zweig behoben wurde.


Der erste Fehler betrifft den Umgang des werkzeug-Entwicklungsservers mit Zeilenumbruch-Headern. Es kann mit dem folgenden Servercode reproduziert werden, der den Wert des X-Example Headers ausgibt:

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)

Wir können ihm dann eine Anfrage mit einem mehrzeiligen Header senden:

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

Erwartete Serverausgabe:
Der Kopfzeilenwert wird in einer einzigen Zeile zusammengeführt

'foo bar'

Tatsächliche Serverausgabe (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
----------------------------------------

Tatsächliche Serverausgabe (Python 3):
Der Header-Wert enthält Newline-Zeichen, die von der WSGI-Spezifikation nicht zugelassen sind

'foo\r\n bar'

Der zweite Fehler hat damit zu tun, wie das Headers Objekt mit Zeilenumbruchwerten umgeht.

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

Erwartetes Ergebnis:
Beide Operationen sollten das gleiche Ergebnis haben, egal ob ValueError oder Erfolg.

Tatsächliche Ergebnis:
Headers Konstruktor erlaubt Zeilenumbrüche, während Headers.add() ValueError erhöht.

Alle 8 Kommentare

HTTP-Header erlauben keine Zeilenumbrüche. In dem von Ihnen zitierten Abschnitt geht es um das Falten, das Zeilenumbrüche aus dem entfalteten Wert entfernen sollte. Diese:

"""foo
     bar"""

sollte sich in etwa so entfalten:

"foo bar"

Abgesehen davon parst Werkzeug auf dieser Ebene kein HTTP, dies ist die Aufgabe des WSGI-Servers. Der einzige Grund, warum es beim Parsen von Anforderungen Zeilenumbrüche ablehnt, besteht darin, Sicherheitsprobleme zu erkennen.

Dieses Ticket wurde durch das reale Verhalten von Flask im Entwicklungsmodus hinter einem Nginx-Proxy motiviert, der Client-Zertifikate weiterleitet. Bei diesem Setup habe ich beobachtet, dass Zeilenumbrüche in den Headern an die Anwendung übergeben werden. Aber als ich versuchte, dies in einem Komponententest zu replizieren, erhielt ich beim Erstellen der Anforderungsheader das obige ValueError .

Ich habe etwas mehr zu diesem Thema recherchiert und folgendes gefunden:

  • Die HTTP-Spezifikation ( RFC 2616 ) besagt, dass Zeilenumbrüche in Headern durch ein einzelnes Leerzeichen ersetzt werden können, dies jedoch nicht garantiert ist (siehe Zitat oben).

  • Die CGI-Spezifikation ( RFC 3875 ), die WSGI erweitert, erfordert das Ersetzen von Zeilenumbrüchen in Anforderungsheadern:

    Ebenso MUSS ein Kopfzeilenfeld, das sich über mehrere Zeilen erstreckt, zu einer einzigen Zeile zusammengeführt werden.

  • Die WSGI-Spezifikation ( PEP 333 ) verbietet auch Zeilenumbrüche in Antwortheadern:

    Jedes header_value darf keine Steuerzeichen enthalten, einschließlich Carriage Returns oder Linefeeds, weder eingebettet noch am Ende.

Das bedeutet also, dass es hier zwei Fehler gibt:

  • Der werkzeug-Entwicklungsserver normalisiert gefaltete Zeichenfolgen in Anforderungsheadern nicht korrekt.

  • Das Objekt Headers akzeptiert keine Zeilenumbrüche in Headerwerten. Zeilenumbrüche werden im Konstruktor akzeptiert, jedoch nicht in der Methode add() . Es ist wahrscheinlich besser für das Headers Objekt, permissiv zu bleiben und die Validierung durchzuführen, wenn tatsächlich die WSGI-Antwort in BaseResponse . Auf diese Weise wird das Verbieten von Zeilenumbrüchen im Konstruktor die Kompatibilität mit möglicherweise nicht konformen WSGI-Servern (wie dem eigenen Entwicklungsserver von werkzeug) nicht beeinträchtigen.

Meinetwegen. Es gibt auch #1070, die dazu beiträgt.

Ziemlich sicher, dass dies mit dem Fix für #1070 behoben wurde. Wenn nicht, teilen Sie mir dies bitte mit einem reproduzierbaren Beispiel mit.

@davidism Wie ich in einem früheren Kommentar erwähnt habe, gibt es hier tatsächlich zwei Fehler, von denen keiner im aktuellen Master-Zweig behoben wurde.


Der erste Fehler betrifft den Umgang des werkzeug-Entwicklungsservers mit Zeilenumbruch-Headern. Es kann mit dem folgenden Servercode reproduziert werden, der den Wert des X-Example Headers ausgibt:

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)

Wir können ihm dann eine Anfrage mit einem mehrzeiligen Header senden:

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

Erwartete Serverausgabe:
Der Kopfzeilenwert wird in einer einzigen Zeile zusammengeführt

'foo bar'

Tatsächliche Serverausgabe (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
----------------------------------------

Tatsächliche Serverausgabe (Python 3):
Der Header-Wert enthält Newline-Zeichen, die von der WSGI-Spezifikation nicht zugelassen sind

'foo\r\n bar'

Der zweite Fehler hat damit zu tun, wie das Headers Objekt mit Zeilenumbruchwerten umgeht.

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

Erwartetes Ergebnis:
Beide Operationen sollten das gleiche Ergebnis haben, egal ob ValueError oder Erfolg.

Tatsächliche Ergebnis:
Headers Konstruktor erlaubt Zeilenumbrüche, während Headers.add() ValueError erhöht.

Ich habe kürzlich das ValueError in einem unserer Projekte (Python 2.7 mit Flask) gesehen, das zufällig hinter einem Nginx-Proxy sitzt. Am Ende kehrte ich zu 0.14.1 (wodurch die mit Flask gebündelte Version überschrieben wurde) und mein Fehler war verschwunden. Ich dachte, ich würde hinzufügen, da es den Anschein hat, dass der 0.15.x Zweig dieses Problem eingeführt hat (oder möglicherweise ein neues Problem mit der Verarbeitung von Anforderungsheadern verursacht hat).

------AKTUALISIEREN-------:

Ich habe herausgefunden, welche Header wir übergeben haben und einer davon war ein mehrzeiliges Zertifikat im Pem-Format, dh:

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

Unser Nginx-Server wurde wie folgt konfiguriert:

proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;

Wir sollten wahrscheinlich $ssl_client_escaped_cert anstelle von $ssl_client_cert (da dies sowieso veraltet ist). Ich bin mir jedoch nicht sicher, ob diese Änderung das Header-Parsing-Problem behebt.

In der Hoffnung, dass dies jedem anderen hilft, der auf dieses Problem stößt. Es scheint, dass 0.15.1 derzeit mit Python 2.7 einen mehrzeiligen Header wie ein PEM-Zertifikat nicht richtig verarbeitet.

Dies ist wieder ein 2.7-Problem, das durch die Header-Verarbeitung des Entwicklungsservers verursacht wird. Ich füge dem 2.7-Kompatibilitätscode für Anforderungsheader die Möglichkeit hinzu, das Falten von Headern zu verarbeiten.

Bestätigt, dass der Entwicklungsserver-Fehler jetzt sowohl in Python 2 als auch in 3 behoben ist. Das zweite Problem mit dem Headers Objekt ist immer noch vorhanden. Geöffnet #1608.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

caiz picture caiz  ·  3Kommentare

lepture picture lepture  ·  6Kommentare

asottile picture asottile  ·  11Kommentare

SimonSapin picture SimonSapin  ·  12Kommentare

KangOl picture KangOl  ·  16Kommentare