Selon RFC 2616 :
Les valeurs des champs d'en-tête HTTP/1.1 peuvent être repliées sur plusieurs lignes si la ligne de continuation commence par un espace ou une tabulation horizontale. Tous les espaces blancs linéaires, y compris le pliage, ont la même sémantique que SP. Un destinataire PEUT remplacer tout espace blanc linéaire par un seul SP avant d'interpréter la valeur du champ ou de transmettre le message en aval.
Cependant, werkzeug n'accepte pas les valeurs d'en-tête avec des sauts de ligne, même si elles respectent cette convention.
>>> 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
De plus, cette restriction est appliquée de manière incohérente.
>>> werkzeug.Headers([('foo', 'bar\n baz')])
Headers([('foo', 'bar\n baz')])
J'ai rencontré ce problème en essayant d'écrire des cas de test concernant le transfert nginx de certificats client via des en-têtes, il existe donc un cas d'utilisation réel pour le prendre en charge correctement.
Les en-têtes HTTP n'autorisent pas les nouvelles lignes. La section que vous citez parle de pliage, ce qui devrait supprimer les nouvelles lignes de la valeur dépliée. Cette:
"""foo
bar"""
devrait se dérouler à quelque chose comme ceci:
"foo bar"
En dehors de cela, Werkzeug n'analyse pas HTTP à ce niveau, c'est le travail du serveur WSGI. La seule raison pour laquelle il rejette les nouvelles lignes lors de l'analyse des requêtes est de détecter les problèmes de sécurité.
Ce ticket a été motivé par le comportement réel de Flask en mode développement derrière un proxy nginx transférant des certificats client. Avec cette configuration, j'ai observé des nouvelles lignes dans les en-têtes transmises à l'application. Mais lorsque j'ai tenté de reproduire cela dans un test unitaire, j'ai obtenu les ValueError
ci-dessus lors de la création des en-têtes de requête.
J'ai fait un peu plus de recherches sur ce problème et j'ai trouvé ce qui suit :
La spécification HTTP ( RFC 2616 ) indique que les nouvelles lignes dans les en-têtes PEUVENT être remplacées par un seul espace, pas qu'elles soient garanties (voir la citation ci-dessus).
La spécification CGI ( RFC 3875 ), que WSGI étend, nécessite le remplacement des nouvelles lignes dans les en-têtes de requête :
De même, un champ d'en-tête qui s'étend sur plusieurs lignes DOIT être fusionné sur une seule ligne.
La spécification WSGI ( PEP 333 ) interdit également les nouvelles lignes dans les en-têtes de réponse :
Chaque
header_value
ne doit inclure aucun caractère de contrôle, y compris les retours chariot ou les sauts de ligne, qu'ils soient intégrés ou à la fin.
Cela signifie donc qu'il y a deux bugs ici :
Le serveur de développement werkzeug ne normalise pas correctement les chaînes pliées dans les en-têtes de requête.
L'objet Headers
n'accepte pas les nouvelles lignes dans les valeurs d'en-tête. Les sauts de ligne sont acceptés dans le constructeur, mais pas dans la méthode add()
. Il est probablement préférable que l'objet Headers
reste permissif et effectue la validation lors de la construction de la réponse WSGI dans BaseResponse
. De cette façon, interdire les nouvelles lignes dans le constructeur ne rompra pas la compatibilité avec les serveurs WSGI éventuellement non conformes (tels que le propre serveur de développement de werkzeug).
Assez juste. Il y a aussi #1070 qui joue là-dedans.
Assez sûr que cela a été corrigé avec le correctif pour #1070. Si ce n'est pas le cas, faites-le moi savoir avec un exemple reproductible.
@davidism Comme je l'ai mentionné dans un commentaire précédent, il y a en fait deux bogues ici, dont aucun n'a été corrigé sur la branche master actuelle.
Le premier bogue concerne la façon dont le serveur de développement werkzeug gère les en-têtes enroulés en ligne. Il peut être reproduit avec le code serveur suivant, qui imprime la valeur de l'en-tête 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)
Nous pouvons ensuite lui envoyer une requête avec un en-tête sur plusieurs lignes :
GET / HTTP/1.1
Host: localhost:8080
Connection: close
X-Example: foo
bar
Sortie serveur attendue :
La valeur de l'en-tête est fusionnée sur une seule ligne
'foo bar'
Sortie réelle du serveur (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
----------------------------------------
Sortie réelle du serveur (Python 3) :
La valeur d'en-tête contient des caractères de nouvelle ligne non autorisés par la spécification WSGI
'foo\r\n bar'
Le deuxième bogue concerne la façon dont l'objet Headers
gère les valeurs de nouvelle ligne.
>>> 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
Résultat attendu:
Les deux opérations doivent avoir le même résultat, qu'il s'agisse d'un ValueError
ou d'un succès.
Résultat actuel:
Headers
autorise les nouvelles lignes tandis que Headers.add()
augmente ValueError
.
Je voyais le ValueError
récemment dans l'un de nos projets (Python 2.7 avec Flask) qui se trouve derrière un proxy nginx. J'ai fini par revenir à 0.14.1
(en remplaçant la version fournie avec Flask
) et mon erreur a disparu. J'ai pensé ajouter car il semble que la branche 0.15.x
introduit ce problème (ou ait potentiellement créé un nouveau problème avec la façon dont elle gérait les en-têtes de demande).
------METTRE À JOUR-------:
J'ai recherché les en-têtes que nous passions et l'un d'eux était un certificat multiligne au format pem, c'est-à-dire :
SSL_CLIENT_CERT: -----BEGIN CERTIFICATE-----
MIIFHzCCAwegAwIBAgICEDgwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCdXMx
GDAWBgNVBAoMD3Uucy4gZ292ZXJubWVudDEPMA0GA1UECwwGcGVvcGxlMQwwCgYD
VQQLDANkYWUxEDAOBgNVBAsMB2NoaW1lcmExEDAOBgNVBAMMB0ludGVyQ0EwHhcN
MTcwODMxMTUwMzEwWhcNMjcwODI5MTUwMzEwWjBwMQswCQYDVQQGEwJVUzEYMBYG
A1UECgwPVS5TLiBHb3Zlcm5tZW50MRAwDgYDVQQLDAdjaGltZXJhMQwwCgYDVQQL
....
-----END CERTIFICATE-----
Notre serveur nginx a été configuré comme suit :
proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;
Nous devrions probablement utiliser $ssl_client_escaped_cert
au lieu de $ssl_client_cert
(puisque c'est de toute façon obsolète). Je ne sais pas si ce changement résoudra le problème d'analyse d'en-tête.
En espérant que cela aide quelqu'un d'autre qui rencontre ce problème. Il semble que 0.15.1
ne gère pas correctement un en-tête multiligne comme un certificat PEM pour le moment avec Python 2.7.
Il s'agit à nouveau d'un problème 2.7 causé par le traitement des en-têtes du serveur de développement. J'ajoute la possibilité de traiter le pliage des en-têtes au code de compatibilité 2.7 pour les en-têtes de demande.
Confirmé que le bogue du serveur de développement est maintenant corrigé dans Python 2 et 3. Le deuxième problème avec l'objet Headers
est toujours présent. Ouvert #1608.
Commentaire le plus utile
@davidism Comme je l'ai mentionné dans un commentaire précédent, il y a en fait deux bogues ici, dont aucun n'a été corrigé sur la branche master actuelle.
Le premier bogue concerne la façon dont le serveur de développement werkzeug gère les en-têtes enroulés en ligne. Il peut être reproduit avec le code serveur suivant, qui imprime la valeur de l'en-tête
X-Example
:Nous pouvons ensuite lui envoyer une requête avec un en-tête sur plusieurs lignes :
Sortie serveur attendue :
La valeur de l'en-tête est fusionnée sur une seule ligne
Sortie réelle du serveur (Python 2) :
Sortie réelle du serveur (Python 3) :
La valeur d'en-tête contient des caractères de nouvelle ligne non autorisés par la spécification WSGI
Le deuxième bogue concerne la façon dont l'objet
Headers
gère les valeurs de nouvelle ligne.Résultat attendu:
Les deux opérations doivent avoir le même résultat, qu'il s'agisse d'un
ValueError
ou d'un succès.Résultat actuel:
Headers
autorise les nouvelles lignes tandis queHeaders.add()
augmenteValueError
.