X-Forwarded-For es una cabecera HTTP extendida. El protocolo HTTP/1.1 (RFC 2616) no la define; fue introducida originalmente por el software de proxy de caché Squid, para indicar la IP real del lado solicitante de la solicitud HTTP. Hoy en día se ha convertido en un estándar de facto, ampliamente utilizada por diversos servicios de reenvío como proxies HTTP y balanceadores de carga, y ha sido incluida en el estándar RFC 7239 (Forwarded HTTP Extension).
El formato de la cabecera de solicitud X-Forwarded-For es muy simple, así:
X-Forwarded-For: client, proxy1, proxy2
Se puede ver que el contenido de XFF está compuesto por varias partes separadas por «coma + espacio en inglés»; la primera es la IP del dispositivo más alejado del servidor, seguida de la IP de cada nivel de dispositivo proxy.
Si una solicitud HTTP pasa a través de tres proxies Proxy1, Proxy2, Proxy3 antes de llegar al servidor, con IPs IP1, IP2, IP3 respectivamente, y la IP real del usuario es IP0, entonces según el estándar XFF, el servidor finalmente recibirá la siguiente información:
X-Forwarded-For: IP0, IP1, IP2
Proxy3 está conectado directamente al servidor; añadirá IP2 a XFF, indicando que está reenviando la solicitud en nombre de Proxy2. La lista no incluye IP3; IP3 se puede obtener en el servidor a través del campo Remote Address. Sabemos que una conexión HTTP se basa en una conexión TCP, y el protocolo HTTP no tiene el concepto de IP; Remote Address proviene de la conexión TCP e indica la IP del dispositivo que estableció la conexión TCP con el servidor, que en este ejemplo es IP3.
Remote Address no se puede falsificar, porque establecer una conexión TCP requiere un protocolo de enlace de tres pasos; si se falsifica la IP de origen, no se puede establecer la conexión TCP y mucho menos habrá una solicitud HTTP posterior. Diferentes lenguajes obtienen Remote Address de diferentes maneras; por ejemplo, en PHP es $_SERVER["REMOTE_ADDR"], en Node.js es req.connection.remoteAddress, pero el principio es el mismo.
Problema
Con los antecedentes anteriores, empezamos a hablar del problema. Escribí un servidor web muy simple en Node.js para probar. El protocolo HTTP no está relacionado con el lenguaje; usar Node.js aquí es solo para facilitar la demostración; se puede cambiar a cualquier otro lenguaje y se obtendrá la misma conclusión. Además, este artículo también aplica a Nginx; si está interesado, puede cambiarlo a Apache u otro servidor web y será lo mismo.
El siguiente código escuchará en el puerto 9009 y, después de recibir una solicitud HTTP, mostrará alguna información:
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');
Además de Remote Address y X-Forwarded-For introducidos anteriormente, este código también tiene X-Real-IP, que es otro campo de cabecera personalizado. X-Real-IP suele ser utilizado por los proxies HTTP para indicar la IP del dispositivo que estableció la conexión TCP con él; este dispositivo puede ser otro proxy o el verdadero solicitante. Cabe señalar que X-Real-IP actualmente no pertenece a ningún estándar; los proxies y las aplicaciones web pueden acordar usar cualquier cabecera personalizada para transmitir esta información.
Ahora puede acceder directamente al servicio Node.js usando dominio + número de puerto, y configurar un proxy inverso 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;
}
Mi Nginx escucha en el puerto 80, por lo que puede acceder al servicio reenviado por Nginx sin puerto.
Prueba de acceso directo al servicio Node:
curl http://t1.imququ.com:9009/ remoteAddress: 114.248.238.236 x-forwarded-for: undefined x-real-ip: undefined
Como mi computadora se conecta directamente al servicio Node.js, Remote Address es mi IP. Además, no especifiqué cabeceras personalizadas adicionales, por lo que los dos últimos campos son undefined.
Ahora accedamos al servicio reenviado por 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
Esta vez, mi computadora accede al servicio Node.js a través de Nginx, y la Remote Address obtenida es en realidad la IP local de Nginx. Y las dos líneas en la configuración de Nginx anterior funcionaron, añadiendo dos cabeceras personalizadas adicionales a la solicitud:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
En realidad, en la producción, el despliegue de aplicaciones web generalmente usa el segundo método, que tiene muchas ventajas. Pero esto introduce un peligro oculto: muchas aplicaciones web obtienen la IP real del usuario desde las cabeceras HTTP.
Las cabeceras HTTP se pueden construir arbitrariamente. Usamos el parámetro -H de curl para construir X-Forwarded-For y X-Real-IP, y probemos de nuevo.
Acceso directo al servicio 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
Para la aplicación web, X-Forwarded-For y X-Real-IP son solo dos cabeceras de solicitud normales, por lo que naturalmente se emiten sin ningún procesamiento. Esto demuestra que, en el modo de conexión directa, aparte de la Remote Address obtenida de la conexión TCP, la información de IP contenida en las cabeceras de solicitud no es confiable.
Acceso al servicio reenviado por 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
Esta vez, Nginx añadirá mi IP después de X-Forwarded-For; y usará mi IP para sobrescribir la cabecera de solicitud X-Real-IP. Esto demuestra que, con el procesamiento de Nginx, la última sección de X-Forwarded-For y todo el contenido de X-Real-IP no se pueden falsificar, y se pueden usar para obtener la IP del usuario.
La IP del usuario a menudo se usa en escenarios relacionados con la seguridad web, como verificar la región de inicio de sesión del usuario, control de frecuencia de acceso basado en IP, etc. En tales escenarios, es más importante asegurarse de que la IP no se pueda falsificar. Según las pruebas y el análisis anteriores, para aplicaciones web implementadas y expuestas directamente al usuario, se debe usar la Remote Address obtenida de la conexión TCP; para aplicaciones web implementadas con un proxy inverso como Nginx, después de configurar correctamente el comportamiento de Set Header, se puede usar la última sección de X-Real-IP o X-Forwarded-For transmitida por Nginx (en realidad, son equivalentes).
Entonces, ¿cómo puede la propia aplicación web determinar si la solicitud viene directamente o es reenviada por un proxy controlable? Agregar cabeceras de solicitud adicionales durante el reenvío del proxy es un método, pero no es muy seguro, porque las cabeceras de solicitud son demasiado fáciles de construir. Si realmente se debe usar así, esta cabecera personalizada debe ser lo suficientemente larga y poco común, y debe guardarse bien sin que se filtre.
Determinar si Remote Address es una IP local también es un método, pero no es perfecto, porque al acceder desde el servidor donde se encuentra Nginx, ya sea acceso directo o a través del proxy Nginx, Remote Address será 127.0.0.1. Este problema generalmente se puede ignorar; lo más problemático es que el servidor proxy inverso y la aplicación web real no necesariamente están implementados en el mismo servidor. Por lo tanto, un enfoque más razonable es recopilar la lista de IPs de todos los servidores proxy; la aplicación web compara una por una después de obtener la Remote Address para determinar de qué manera se accede.
Generalmente, para simplificar la lógica, en producción se bloquean las formas de acceder directamente a la aplicación web a través de un puerto, permitiendo solo el acceso a través de Nginx. Entonces, ¿no hay problema así? No necesariamente.
En primer lugar, si el usuario realmente accede a Nginx a través de un proxy, la última sección de X-Forwarded-For y X-Real-IP obtienen la IP del proxy, que es la única que se puede usar en escenarios relacionados con la seguridad. Pero en algunos escenarios, como mostrar el clima local según la IP, es necesario obtener la IP real del usuario tanto como sea posible; en este momento, la primera IP en X-Forwarded-For puede ser útil. Aquí hay que tener en cuenta un problema; todavía usamos el ejemplo anterior para probar:
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 última sección de X-Forwarded-For es agregada por Nginx, pero la parte anterior proviene de la cabecera de solicitud que recibió Nginx; esta parte es entrada del usuario y no es confiable en absoluto. Se debe tener especial cuidado al usarla; solo se puede usar si cumple con el formato de IP; de lo contrario, puede causar vulnerabilidades de seguridad como inyección SQL o XSS.
Conclusión
- Para aplicaciones web que brindan servicios directamente al exterior, al realizar operaciones relacionadas con la seguridad, solo se puede obtener la IP a través de Remote Address; no se puede confiar en ninguna cabecera de solicitud;
- Para aplicaciones web que utilizan servidores web como Nginx para proxy inverso, con la configuración correcta, se debe usar
X-Forwarded-Forla última sección oX-Real-IPpara obtener la IP (porque Remote Address es la IP de la intranet del servidor donde se encuentra Nginx); al mismo tiempo, también se debe prohibir que la aplicación web brinde servicios directamente al exterior; - En escenarios no relacionados con la seguridad, como mostrar el clima de la ubicación según la IP, se puede obtener de
X-Forwarded-Forlas primeras posiciones para obtener la IP, pero es necesario verificar que el formato de la IP sea legítimo;
PS: Algunos artículos en Internet sugieren configurar Nginx de esta manera, pero en realidad no es razonable:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
Después de esta configuración, la seguridad realmente mejora, pero también hace que se borre toda la información de los proxies antes de que la solicitud llegue a Nginx, impidiendo brindar un mejor servicio a los usuarios que realmente usan proxies. Aun así, se debe entender el principio detrás de esto y analizar cada escenario específico.
Dirección original: https://imququ.com/post/x-forwarded-for-header-in-http.html