Werkzeug: Werkzeug maneja incorrectamente los encabezados de varias líneas

Creado en 10 mar. 2017  ·  8Comentarios  ·  Fuente: pallets/werkzeug

Según RFC 2616 :

Los valores del campo de encabezado HTTP / 1.1 se pueden plegar en varias líneas si la línea de continuación comienza con un espacio o una pestaña horizontal. Todos los espacios en blanco lineales, incluido el plegado, tienen la misma semántica que SP. Un destinatario PUEDE reemplazar cualquier espacio en blanco lineal con un solo SP antes de interpretar el valor del campo o reenviar el mensaje en sentido descendente.

Sin embargo, werkzeug no acepta valores de encabezado con nuevas líneas, incluso si cumplen con esta convención.

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

Además, esta restricción se aplica de manera inconsistente.

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

Me encontré con este problema al intentar escribir casos de prueba relacionados con el reenvío nginx de certificados de cliente a través de encabezados, por lo que existe un caso de uso real para respaldar esto correctamente.

bug server

Comentario más útil

@davidism Como mencioné en un comentario anterior, en realidad hay dos errores aquí, ninguno de los cuales se ha corregido en la rama maestra actual.


El primer error involucra cómo el servidor de desarrollo werkzeug maneja los encabezados envueltos en línea. Se puede reproducir con el siguiente código de servidor, que imprime el valor del encabezado 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)

Luego podemos enviarle una solicitud con un encabezado que abarque varias líneas:

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

Salida esperada del servidor:
El valor del encabezado se fusiona en una sola línea

'foo bar'

Salida real del servidor (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
----------------------------------------

Salida real del servidor (Python 3):
El valor del encabezado contiene caracteres de nueva línea no permitidos por la especificación WSGI

'foo\r\n bar'

El segundo error tiene que ver con cómo el objeto Headers maneja los valores de nueva línea.

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

Resultado Esperado:
Ambas operaciones deben tener el mismo resultado, ya sea un ValueError o un éxito.

Resultado actual:
Headers constructor permite nuevas líneas mientras que Headers.add() aumenta ValueError .

Todos 8 comentarios

Los encabezados HTTP no permiten nuevas líneas. La sección que está citando habla sobre el plegado, que debería eliminar las nuevas líneas del valor desplegado. Esto:

"""foo
     bar"""

debería desplegarse a algo como esto:

"foo bar"

Aparte de eso, Werkzeug no analiza HTTP en este nivel, este es el trabajo del servidor WSGI. La única razón por la que rechaza las nuevas líneas al analizar las solicitudes es para detectar problemas de seguridad.

Este ticket fue motivado por el comportamiento de la vida real de Flask en el modo de desarrollo detrás de un certificado de cliente de reenvío de proxy nginx. Con esa configuración, observé nuevas líneas en los encabezados que se pasaban a la aplicación. Pero cuando intenté replicar esto en una prueba unitaria, obtuve el ValueError al crear los encabezados de solicitud.

Investigué un poco más sobre este tema y encontré lo siguiente:

  • La especificación HTTP ( RFC 2616 ) establece que las nuevas líneas en los encabezados PUEDEN reemplazarse con un solo espacio, no es que se garantice que lo sean (ver cita anterior).

  • La especificación CGI ( RFC 3875 ), que WSGI extiende, requiere que se reemplacen las líneas nuevas en los encabezados de solicitud:

    De manera similar, un campo de encabezado que abarca varias líneas DEBE fusionarse en una sola línea.

  • La especificación WSGI ( PEP 333 ) también prohíbe nuevas líneas en los encabezados de respuesta:

    Cada header_value no debe incluir ningún carácter de control, incluidos retornos de carro o avances de línea, ya sea incrustados o al final.

Entonces esto significa que hay dos errores aquí:

  • El servidor de desarrollo werkzeug no normaliza correctamente las cadenas dobladas en los encabezados de las solicitudes.

  • El objeto Headers es coherente con la aceptación de nuevas líneas en los valores del encabezado. Se aceptan líneas nuevas en el constructor, pero no en el método add() . Probablemente sea mejor que el objeto Headers permanezca permisivo y realice la validación cuando se construya la respuesta WSGI en BaseResponse . De esa manera, prohibir las nuevas líneas en el constructor no romperá la compatibilidad con servidores WSGI posiblemente no conformes (como el propio servidor de desarrollo de werkzeug).

Lo suficientemente justo. También hay # 1070 que juega en esto.

Estoy bastante seguro de que esto se solucionó con la corrección para # 1070. Si no es así, hágamelo saber con un ejemplo reproducible.

@davidism Como mencioné en un comentario anterior, en realidad hay dos errores aquí, ninguno de los cuales se ha corregido en la rama maestra actual.


El primer error involucra cómo el servidor de desarrollo werkzeug maneja los encabezados envueltos en línea. Se puede reproducir con el siguiente código de servidor, que imprime el valor del encabezado 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)

Luego podemos enviarle una solicitud con un encabezado que abarque varias líneas:

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

Salida esperada del servidor:
El valor del encabezado se fusiona en una sola línea

'foo bar'

Salida real del servidor (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
----------------------------------------

Salida real del servidor (Python 3):
El valor del encabezado contiene caracteres de nueva línea no permitidos por la especificación WSGI

'foo\r\n bar'

El segundo error tiene que ver con cómo el objeto Headers maneja los valores de nueva línea.

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

Resultado Esperado:
Ambas operaciones deben tener el mismo resultado, ya sea un ValueError o un éxito.

Resultado actual:
Headers constructor permite nuevas líneas mientras que Headers.add() aumenta ValueError .

Estuve viendo el ValueError recientemente en uno de nuestros proyectos (Python 2.7 con Flask) que se encuentra detrás de un proxy nginx. Terminé volviendo a 0.14.1 (anulando la versión incluida con Flask ) y mi error desapareció. Pensé en agregar ya que parece que la rama 0.15.x introdujo este problema (o potencialmente creó un nuevo problema con la forma en que manejaba los encabezados de solicitud).

------ACTUALIZAR-------:

Rastreé los encabezados que estábamos pasando y uno de ellos era un certificado de varias líneas en formato pem, es decir:

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

Nuestro servidor nginx se configuró así:

proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;

Probablemente deberíamos usar $ssl_client_escaped_cert lugar de $ssl_client_cert (ya que esto está obsoleto de todos modos). Sin embargo, no estoy seguro de si ese cambio solucionará el problema de análisis del encabezado.

Espero que esto ayude a cualquier otra persona que tenga este problema. Parece que 0.15.1 no maneja correctamente un encabezado de varias líneas como un certificado PEM en este momento con Python 2.7.

Este es un problema 2.7 nuevamente causado por el procesamiento de encabezados del servidor de desarrollo. Estoy agregando la capacidad de procesar el plegado de encabezados al código de compatibilidad 2.7 para encabezados de solicitud.

Se confirmó que el error del servidor de desarrollo ahora está solucionado tanto en Python 2 como en 3. El segundo problema con el objeto Headers todavía está presente. Inaugurado # 1608.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

davidism picture davidism  ·  9Comentarios

Nessphoro picture Nessphoro  ·  6Comentarios

golf-player picture golf-player  ·  10Comentarios

paihu picture paihu  ·  7Comentarios

sorenh picture sorenh  ·  4Comentarios