X-Forwarded-For est un en-tête d'extension HTTP. Le protocole HTTP/1.1 (RFC 2616) ne le définit pas. Il a été introduit à l'origine par Squid, un logiciel de proxy cache, pour représenter l'IP réelle du demandeur HTTP. Aujourd'hui, il est devenu un standard de facto, largement utilisé par les proxys HTTP, les équilibreurs de charge et d'autres services de transfert, et a été intégré dans la norme RFC 7239 (Forwarded HTTP Extension).

Le format de l'en-tête de requête X-Forwarded-For est très simple, comme ceci :

X-Forwarded-For: client, proxy1, proxy2

Comme vous pouvez le voir, le contenu de XFF est constitué de plusieurs parties séparées par « virgule + espace ». La première est l'IP de l'appareil le plus éloigné du serveur, suivie de l'IP de chaque appareil proxy.

Si une requête HTTP passe par trois proxys Proxy1, Proxy2 et Proxy3 avant d'atteindre le serveur, avec respectivement les IP IP1, IP2 et IP3, et que l'IP réelle de l'utilisateur est IP0, alors selon la norme XFF, le serveur recevra finalement les informations suivantes :

X-Forwarded-For: IP0, IP1, IP2

Proxy3 se connecte directement au serveur. Il ajoute IP2 à XFF, indiquant qu'il transmet la requête pour le compte de Proxy2. IP3 ne figure pas dans la liste ; IP3 peut être obtenu côté serveur via le champ Remote Address. Nous savons que les connexions HTTP sont basées sur des connexions TCP. Le protocole HTTP n'a pas de notion d'IP. Remote Address provient de la connexion TCP et représente l'IP de l'appareil qui a établi la connexion TCP avec le serveur. Dans cet exemple, il s'agit d'IP3.

L'adresse distante ne peut pas être falsifiée, car l'établissement d'une connexion TCP nécessite une poignée de main en trois étapes. Si l'IP source est falsifiée, la connexion TCP ne peut pas être établie et il n'y aura aucune requête HTTP ultérieure. Différents langages obtiennent l'adresse distante de manières différentes. Par exemple, PHP utilise $_SERVER["REMOTE_ADDR"], Node.js utilise req.connection.remoteAddress, mais le principe est le même.


Problème

Avec les connaissances préalables ci-dessus, commençons par aborder le problème. J'ai écrit un serveur Web très simple en Node.js pour effectuer des tests. Le protocole HTTP est indépendant du langage. Node.js n'est utilisé ici que par commodité ; tout autre langage peut aboutir à la même conclusion. Il en va de même pour Nginx dans cet article. Si cela vous intéresse, Apache ou d'autres serveurs Web fonctionneraient aussi.

Le code suivant écoute sur le port 9009 et affiche certaines informations après avoir reçu une requête HTTP :

var http = require('http');

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('remoteAddress: ' + req.connection.remoteAddress + '\n');
    res.write('x-forwarded-for: ' + req.headers['x-forwarded-for'] + '\n');
    res.write('x-real-ip: ' + req.headers['x-real-ip'] + '\n');
    res.end();
}).listen(9009, '0.0.0.0');

En plus de l'adresse distante et de X-Forwarded-For présentés précédemment, ce code comporte également un X-Real-IP, qui est un autre champ d'en-tête personnalisé. X-Real-IP est souvent utilisé par les proxys HTTP pour représenter l'IP de l'appareil qui a établi une connexion TCP avec lui. Cet appareil peut être un autre proxy ou le demandeur réel. Notez que X-Real-IP ne fait actuellement partie d'aucune norme. Les proxys et les applications Web peuvent convenir d'utiliser n'importe quel en-tête personnalisé pour transmettre cette information.

Vous pouvez maintenant accéder directement à ce service Node.js en utilisant le nom de domaine + le numéro de port, et également configurer un proxy inverse Nginx :

location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;

    proxy_pass http://127.0.0.1:9009/;
    proxy_redirect off;
}

Mon Nginx écoute sur le port 80, je peux donc accéder au service relayé par Nginx sans port.

Testez l'accès direct au service Node :

curl http://t1.imququ.com:9009/

remoteAddress: 114.248.238.236
x-forwarded-for: undefined
x-real-ip: undefined

Comme mon ordinateur s'est directement connecté au service Node.js, l'adresse distante est mon IP. Je n'ai spécifié aucun en-tête personnalisé supplémentaire, donc les deux derniers champs sont tous deux undefined.

Accédez maintenant au service relayé par Nginx :

curl http://t1.imququ.com/

remoteAddress: 127.0.0.1
x-forwarded-for: 114.248.238.236
x-real-ip: 114.248.238.236

Cette fois, mon ordinateur accède au service Node.js via Nginx, et le Remote Address obtenu est en fait l'IP locale de Nginx. Les deux lignes de la configuration Nginx précédente ont pris effet, ajoutant deux en-têtes personnalisés à la requête :

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

En fait, dans les environnements de production, les applications Web sont généralement déployées de la deuxième manière ci-dessus, ce qui présente de nombreux avantages. Mais cela introduit un danger caché : de nombreuses applications Web obtiennent l'IP réelle de l'utilisateur en la prenant dans les en-têtes de requête HTTP.

Les en-têtes de requête HTTP peuvent être construits arbitrairement. Utilisons le paramètre -H de curl pour construire X-Forwarded-For et X-Real-IP, puis testons à nouveau.

Accéder directement au service Node.js :

curl http://t1.imququ.com:9009/ -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'

remoteAddress: 114.248.238.236
x-forwarded-for: 1.1.1.1
x-real-ip: 2.2.2.2

Pour les applications Web, X-Forwarded-For et X-Real-IP ne sont que deux en-têtes de requête ordinaires, ils sont donc affichés tels quels, sans aucun traitement. Cela montre que pour un déploiement en connexion directe, hormis le Remote Address obtenu à partir de la connexion TCP, les informations d'IP portées dans les en-têtes de requête ne peuvent pas être considérées comme fiables.

Accéder au service relayé par Nginx :

curl http://t1.imququ.com/ -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'

remoteAddress: 127.0.0.1
x-forwarded-for: 1.1.1.1, 114.248.238.236
x-real-ip: 114.248.238.236

Cette fois, Nginx ajoute mon IP après X-Forwarded-For et écrase l'en-tête de requête X-Real-IP avec mon IP. Cela montre qu'avec le traitement de Nginx, la dernière section de X-Forwarded-For et tout le contenu de X-Real-IP ne peuvent pas être falsifiés et peuvent être utilisés pour obtenir l'IP de l'utilisateur.

L'IP de l'utilisateur est souvent utilisée dans des scénarios liés à la sécurité Web, comme la vérification de la région de connexion de l'utilisateur et le contrôle de la fréquence d'accès basé sur l'IP. Dans de tels scénarios, garantir que l'IP ne peut pas être falsifiée est plus important. D'après les tests et analyses précédents, pour les applications Web déployées directement auprès des utilisateurs, vous devez utiliser le Remote Address obtenu à partir de la connexion TCP. Pour les applications Web déployées derrière un proxy inverse comme Nginx, après avoir correctement configuré le comportement Set Header, vous pouvez utiliser le X-Real-IP ou la dernière section de X-Forwarded-For transmise par Nginx (en fait, elles sont équivalentes).

Alors, comment une application Web détermine-t-elle elle-même si une requête est arrivée directement ou a été transmise par un proxy contrôlable ? Ajouter un en-tête de requête supplémentaire lors de la transmission par proxy est une solution, mais elle n'est pas très fiable, car les en-têtes de requête sont trop faciles à falsifier. Si vous devez utiliser cette approche, l'en-tête personnalisé doit être suffisamment long et suffisamment rare, et aussi être gardé en sécurité afin qu'il ne soit pas divulgué.

Vérifier si Remote Address est une IP locale est aussi une méthode, mais elle n'est pas parfaite, car en accédant depuis le serveur où réside Nginx, que ce soit directement ou via le proxy Nginx, le Remote Address est toujours 127.0.0.1. Ce problème est généralement négligeable. Plus problématique encore, le serveur proxy inverse et l'application Web réelle peuvent ne pas être déployés sur le même serveur. Par conséquent, une approche plus raisonnable consiste à maintenir une liste de toutes les IP des serveurs proxy. Après que l'application Web a obtenu le Remote Address, elle peut le comparer à la liste, élément par élément, pour déterminer comment la requête a été effectuée.

Habituellement, pour simplifier la logique, les environnements de production bloquent l'accès direct aux applications Web via un port, et n'autorisent l'accès que via Nginx. Cela signifie-t-il qu'il n'y a aucun problème ? Pas nécessairement.

Premièrement, si l'utilisateur accède réellement à Nginx via un proxy, le dernier segment de X-Forwarded-For et de X-Real-IP obtiendra l'IP du proxy. Les scénarios liés à la sécurité ne peuvent utiliser que celle-ci, mais certains scénarios, comme afficher la météo locale en fonction de l'IP, doivent obtenir l'IP réelle de l'utilisateur autant que possible. À ce moment-là, la première IP dans X-Forwarded-For peut être utile. À ce stade, nous devons faire attention à un problème. Utilisons l'exemple précédent pour tester :

curl http://t1.imququ.com/ -H 'X-Forwarded-For: unknown, <>"1.1.1.1'

remoteAddress: 127.0.0.1
x-forwarded-for: unknown, <>"1.1.1.1, 114.248.238.236
x-real-ip: 114.248.238.236

La dernière section de X-Forwarded-For est ajoutée par Nginx, mais les parties précédentes proviennent toutes des en-têtes de requête que Nginx a reçus. Cette partie est une entrée utilisateur et totalement non fiable. Vous devez être extrêmement prudent lorsque vous l'utilisez. Ne l'utilisez que si elle correspond au format IP ; sinon, elle peut facilement causer des vulnérabilités de sécurité telles que l'injection SQL ou le XSS.

Conclusion

  1. Les applications Web qui fournissent directement des services au monde extérieur ne peuvent obtenir l'IP que via Remote Address lors d'opérations liées à la sécurité, et ne peuvent faire confiance à aucun en-tête de requête ;
  2. Les applications Web utilisant Nginx ou d'autres serveurs Web pour un proxy inverse, avec une configuration correcte, doivent utiliserX-Forwarded-Forla dernière section ouX-Real-IPpour obtenir l'IP (car Remote Address obtient l'IP interne du serveur où Nginx est situé) ; en même temps, l'accès externe direct à l'application Web doit être interdit ;
  3. Dans les scénarios non liés à la sécurité, comme l'affichage de la météo locale par IP, vous pouvez obtenir l'IP à partir deX-Forwarded-Forune position antérieure, mais vous devez valider le format de l'IP.

PS : certains articles en ligne suggèrent de configurer Nginx comme ceci, mais ce n'est en fait pas raisonnable :

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;

Avec cette configuration, la sécurité est certes améliorée, mais elle efface aussi toutes les informations de proxy avant que la requête n'atteigne Nginx, ce qui rend impossible la fourniture de meilleurs services aux utilisateurs qui utilisent réellement des proxies. Vous devez toujours comprendre les principes sous-jacents et analyser les scénarios spécifiques au cas par cas.

Adresse originale : https://imququ.com/post/x-forwarded-for-header-in-http.html