Symfony, Apache, headers et underscore

Quand on met des contraintes qui n'ont pas lieu d'être.

Aujourd’hui, je souhaite tester une route que j’ai créé dans mon application Symfony. J’utilise Postman. Ma requête est un POST, avec des headers. Jusque là, rien d’anormal.

L’un des headers est composé de plusieurs mots, que je concatène donc avec des underscores. J’ai donc un header qui s’appelle mon_header. Pareil, rien d’extraordinaire. En fait, c’est même autorisé par HTTP. Je cite le RFC 822 pour prouver.

The field-name must be composed of printable ASCII characters (i.e., characters that have values between 33. and 126., decimal, except colon)

Le underscore est donc un caractère valide. Mais Apache et Symfony en décide autrement. En premier lieu, Apache supprime silencieusement tout header contenant des underscores. C’est le cas dans la 2.4, version utilisée par ce projet.

Translation of headers to environment variables is more strict than before to mitigate some possible cross-site-scripting attacks via header injection. Headers containing invalid characters (including underscores) are now silently dropped.

Release note

Puis en deuxième lieu, Symfony remplace automatiquement les majuscules par des minuscules et les underscores par des tirets**. Sans aucune raison et sans possibilité d’influer sur ce comportement. C’est ce qu’on retrouve dans ce fichier.

protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    protected const LOWER = '-abcdefghijklmnopqrstuvwxyz';

public function all(string $key = null)
    {
        if (null !== $key) {
            return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? [];
        }
        return $this->headers;
    }

Je ne connais pas les raisons de ces comportements. Tout ce que je sais, c’est que j’ai perdu du temps ce matin pour une erreur qui n’existerait pas si certains n’étaient pas plus royalistes que le roi. :-°



7 commentaires

J’ai l’impression qu’ils ne sont pas d’accord entre eux sur la documentation.

Puis en deuxième lieu, Symfony remplace automatiquement les majuscules par des minuscules et les underscores par des tirets**

à quoi servent les deux étoiles ** à la fin de la phrase ? :o

Puis en deuxième lieu, Symfony remplace automatiquement les majuscules par des minuscules et les underscores par des tirets.

J’ai souvenir avoir eu des problèmes concernant les majuscules pour certains headers avec Apache comme Authorization. Au moins Symfony évite, je pense, à beaucoup de développeurs (plus ou moins novices) de se prendre la tête :p

Mais merci pour ce billet sinon ! Très intéressant. Je reste aussi choqué que toi que Apache se permette de telle liberté.

En fait, c’est même autorisé par HTTP. Je cite le RFC 822 pour prouver.

Ce n’est pas une preuve, parce que la RFC 822 a été rendue obsolète par RFC 2822 qui a été elle même rendue obsolète par la RFC 5322. Et si l’ancienne RFC pour HTTP/1.1 (la RFC 2616) faisait référence à la RFC 822, aujourd’hui il faut aller lire les RFC 7230 à 7235, qui ne font pas référence à ces RFCs du tout.

Mais que disent les nouvelles RFCs ? À la section 3.2.4 de la RFC 7230, on peut lire :

Historically, HTTP has allowed field content with text in the ISO-8859–1 charset [ISO-8859–1], supporting other charsets only through use of [RFC2047] encoding. In practice, most HTTP header field values use only a subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD limit their field values to US-ASCII octets. A recipient SHOULD treat other octets in field content (obs-text) as opaque data.

Bon, les specs récentes sont d’accord avec toi : l’underscore devrait être valide. Alors pourquoi est-ce que Apache (et nginx) l’interdisent ?

Il faut revenir aux années 90 et à CGI, que PHP et d’autres utilisent pour communiquer avec les serveurs web. Avec CGI les headers sont passés par variable d’environnement Unix, ce qui empêche d’utiliser les tirets par exemple. La RFC 3875 (CGI 1.1) indique donc ces règles :

Meta-variables with names beginning with "HTTP" contain values read from the client request header fields, if the protocol used is HTTP. The HTTP header field name is converted to upper case, has all occurrences of "-" replaced with "" and has "HTTP_" prepended to give the meta-variable name.

Donc X-Auth-User devient X_AUTH_USER. Mais X-Auth_User devient aussi X_AUTH_USER ! Quel est le problème ? À première vue ça semble bien, et ça obéit à la fameuse maxime "Be liberal in what you accept, and conservative in what you send" de Jon Postel. Mais Postel avait tort, et une des raisons pour lesquelles il avait tort, c’est qu’accepter différentes façon de de dire la même chose donne des vulnérabilités.

En effet, ça donne tellement de possibilités de définir un header qu’il est impossible de filtrer un header sensible convenablement. Et c’est pour ça que nginx et Apache refusent les underscores. D’ailleurs le serveur de développement Django ne le faisait pas, et ça a donné la CVE-2015–0219 qui donne un exemple concret.

Passons à ton deuxième point :

Puis en deuxième lieu, Symfony remplace automatiquement les majuscules par des minuscules et les underscores par des tirets

Les minuscules, c’est parce que la section 3.2 de la RFC 7230 dit:

Each header field consists of a case-insensitive field name followed by a colon (":"), optional leading whitespace, the field value, and optional trailing whitespace.

Et l’underscore par le tiret, c’est parce que Symfony expose les headers HTTP avec un tiret, par exemple $request->headers->get('User-Agent'), et CGI a remplacé par le tiret original par un underscore, donc il faut revenir au tiret.

+3 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte