X-Forwarded-For ist ein HTTP-Erweiterungsheader. Das HTTP/1.1-Protokoll (RFC 2616) definiert ihn nicht. Er wurde ursprünglich von Squid, einer Caching-Proxy-Software, eingeführt, um die echte IP des HTTP-Anfragenden darzustellen. Heute hat er sich als De-facto-Standard etabliert, wird von HTTP-Proxys, Load-Balancern und anderen Weiterleitungsdiensten weit verbreitet verwendet und wurde in den Standard RFC 7239 (Forwarded HTTP Extension) aufgenommen.

Das Format des X-Forwarded-For-Anfrageheaders ist sehr einfach, wie folgt:

X-Forwarded-For: client, proxy1, proxy2

Wie Sie sehen können, besteht der XFF-Inhalt aus mehreren Teilen, die durch „Komma + Leerzeichen“ getrennt sind. Der erste ist die IP des Geräts, das am weitesten vom Server entfernt ist, gefolgt von der IP jedes Proxy-Geräts.

Wenn eine HTTP-Anfrage drei Proxys Proxy1, Proxy2 und Proxy3 durchläuft, bevor sie den Server erreicht, mit den IPs IP1, IP2 bzw. IP3, und die echte IP des Benutzers IP0 ist, dann empfängt der Server gemäß dem XFF-Standard schließlich die folgenden Informationen:

X-Forwarded-For: IP0, IP1, IP2

Proxy3 verbindet sich direkt mit dem Server. Er hängt IP2 an XFF an, was anzeigt, dass er die Anfrage im Namen von Proxy2 weiterleitet. IP3 ist nicht in der Liste; IP3 kann serverseitig über das Feld Remote Address ermittelt werden. Wir wissen, dass HTTP-Verbindungen auf TCP-Verbindungen basieren. Das HTTP-Protokoll hat kein Konzept von IP. Remote Address stammt aus der TCP-Verbindung und repräsentiert die IP des Geräts, das die TCP-Verbindung mit dem Server aufgebaut hat. In diesem Beispiel ist es IP3.

Remote Address kann nicht gefälscht werden, weil der Aufbau einer TCP-Verbindung einen Drei-Wege-Handshake erfordert. Wenn die Quell-IP gefälscht ist, kann die TCP-Verbindung nicht aufgebaut werden, und es wird keine nachfolgende HTTP-Anfrage geben. Verschiedene Sprachen erhalten die Remote Address auf unterschiedliche Weise. Zum Beispiel verwendet PHP $_SERVER["REMOTE_ADDR"], Node.js verwendet req.connection.remoteAddress, aber das Prinzip ist dasselbe.


Problem

Mit dem obigen Hintergrundwissen beginnen wir nun, über das Problem zu sprechen. Ich habe zu Testzwecken einen sehr einfachen Web-Server in Node.js geschrieben. Das HTTP-Protokoll ist sprachunabhängig. Node.js wird hier nur aus Bequemlichkeit verwendet; jede andere Sprache kann zum selben Ergebnis kommen. Dasselbe gilt für Nginx in diesem Artikel. Wenn Sie interessiert sind, würden auch Apache oder andere Web-Server funktionieren.

Der folgende Code lauscht auf Port 9009 und gibt nach dem Empfang einer HTTP-Anfrage einige Informationen aus:

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');

Zusätzlich zur zuvor eingeführten Remote Address und X-Forwarded-For enthält dieser Code auch ein X-Real-IP, ein weiteres benutzerdefiniertes Header-Feld. X-Real-IP wird häufig von HTTP-Proxys verwendet, um die IP des Geräts anzugeben, das eine TCP-Verbindung mit ihm aufgebaut hat. Dieses Gerät kann ein weiterer Proxy oder der tatsächliche Anfragende sein. Beachten Sie, dass X-Real-IP derzeit nicht Teil eines Standards ist. Proxys und Webanwendungen können vereinbaren, einen beliebigen benutzerdefinierten Header zu verwenden, um diese Informationen weiterzugeben.

Jetzt können Sie direkt über den Domänennamen + Portnummer auf diesen Node.js-Dienst zugreifen und außerdem einen Nginx-Reverse-Proxy konfigurieren:

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

Mein Nginx lauscht auf Port 80, daher kann ich auf den von Nginx weitergeleiteten Dienst ohne Port zugreifen.

Testen Sie den direkten Zugriff auf den Node-Dienst:

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

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

Da mein Computer direkt eine Verbindung zum Node.js-Dienst hergestellt hat, ist die Remote Address meine IP. Ich habe keine zusätzlichen benutzerdefinierten Header angegeben, daher sind die letzten beiden Felder beide undefined.

Greifen Sie nun auf den von Nginx weitergeleiteten Dienst zu:

curl http://t1.imququ.com/

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

Dieses Mal greift mein Computer über Nginx auf den Node.js-Dienst zu, und die erhaltene Remote Address ist tatsächlich die lokale IP von Nginx. Die beiden Zeilen in der Nginx-Konfiguration zuvor haben Wirkung gezeigt und der Anfrage zwei benutzerdefinierte Header hinzugefügt:

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

Tatsächlich werden Webanwendungen in Produktionsumgebungen normalerweise auf die zweite oben genannte Weise bereitgestellt, was viele Vorteile hat. Dies birgt jedoch eine versteckte Gefahr: Viele Webanwendungen ermitteln die echte Benutzer-IP, indem sie sie aus HTTP-Request-Headern entnehmen.

HTTP-Request-Header können beliebig konstruiert werden. Verwenden wir den -H-Parameter von curl, um X-Forwarded-For und X-Real-IP zu konstruieren, und testen wir erneut.

Direkter Zugriff auf den Node.js-Dienst:

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

Für Webanwendungen sind X-Forwarded-For und X-Real-IP nur zwei gewöhnliche Request-Header, daher werden sie unverändert ausgegeben, ohne jegliche Verarbeitung. Das zeigt, dass bei einer Direktverbindungs-Bereitstellung außer der aus der TCP-Verbindung erhaltenen Remote Address keine in Request-Headern mitgeführte IP-Information vertrauenswürdig ist.

Zugriff auf den über Nginx weitergeleiteten Dienst:

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

Dieses Mal hängt Nginx meine IP hinter X-Forwarded-For an und überschreibt den X-Real-IP-Request-Header mit meiner IP. Das zeigt, dass bei der Verarbeitung durch Nginx der letzte Abschnitt von X-Forwarded-For und der gesamte Inhalt von X-Real-IP nicht gefälscht werden können und verwendet werden können, um die Benutzer-IP zu ermitteln.

Die Benutzer-IP wird häufig in Szenarien im Zusammenhang mit Web-Sicherheit verwendet, beispielsweise zur Überprüfung der Anmelderegion des Benutzers und zur IP-basierten Zugriffshäufigkeitskontrolle. In solchen Szenarien ist es wichtiger, sicherzustellen, dass die IP nicht gefälscht werden kann. Basierend auf den vorherigen Tests und Analysen müssen Sie für Webanwendungen, die direkt für Benutzer bereitgestellt werden, die aus der TCP-Verbindung erhaltene Remote Address verwenden. Für Webanwendungen, die hinter einem Reverse-Proxy wie Nginx bereitgestellt werden, können Sie nach korrekter Konfiguration des Set-Header-Verhaltens die von Nginx übergebene X-Real-IP oder den letzten Abschnitt von X-Forwarded-For verwenden (tatsächlich sind sie äquivalent).

Wie bestimmt eine Webanwendung selbst, ob eine Anfrage direkt kam oder von einem kontrollierbaren Proxy weitergeleitet wurde? Das Hinzufügen eines zusätzlichen Anfrage-Headers bei der Proxy-Weiterleitung ist eine Möglichkeit, aber sie ist nicht sehr zuverlässig, weil Anfrage-Header zu leicht gefälscht werden können. Wenn dieser Ansatz verwendet werden muss, sollte der benutzerdefinierte Header lang genug und selten genug sein und auch sicher aufbewahrt werden, damit er nicht leckt.

Zu prüfen, ob die Remote Address eine lokale IP ist, ist ebenfalls eine Methode, aber sie ist nicht perfekt, denn beim Zugriff vom Server, auf dem Nginx läuft, egal ob direkt oder über den Nginx-Proxy, ist die Remote Address immer 127.0.0.1. Dieses Problem ist normalerweise vernachlässigbar. Problematischer ist, dass der Reverse-Proxy-Server und die eigentliche Webanwendung möglicherweise nicht auf demselben Server bereitgestellt sind. Daher ist ein sinnvollerer Ansatz, eine Liste aller Proxy-Server-IPs zu pflegen. Nachdem die Webanwendung die Remote Address erhalten hat, kann sie sie nacheinander mit der Liste abgleichen, um zu bestimmen, wie die Anfrage erfolgt ist.

Normalerweise wird in Produktionsumgebungen, um die Logik zu vereinfachen, der direkte Zugriff auf Webanwendungen über einen Port blockiert und nur der Zugriff über Nginx erlaubt. Bedeutet das, dass es keine Probleme gibt? Nicht unbedingt.

Erstens, wenn der Benutzer wirklich über einen Proxy auf Nginx zugreift, erhalten der letzte Abschnitt von X-Forwarded-For und X-Real-IP die IP des Proxys. Sicherheitsrelevante Szenarien können nur dies verwenden, aber in einigen Szenarien, wie z. B. dem Anzeigen des lokalen Wetters basierend auf der IP, muss die echte IP des Benutzers so weit wie möglich ermittelt werden. Zu diesem Zeitpunkt kann die erste IP in X-Forwarded-For nützlich sein. An dieser Stelle müssen wir auf ein Problem achten. Verwenden wir das vorherige Beispiel zum Testen:

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

Der letzte Abschnitt von X-Forwarded-For wird von Nginx angehängt, aber die vorhergehenden Teile stammen alle aus den Request-Headern, die Nginx empfangen hat. Dieser Teil ist Benutzereingabe und völlig nicht vertrauenswürdig. Sie müssen bei der Verwendung besonders vorsichtig sein. Verwenden Sie ihn nur, wenn er dem IP-Format entspricht; andernfalls kann er leicht zu Sicherheitslücken wie SQL-Injection oder XSS führen.

Fazit

  1. Webanwendungen, die Dienste direkt nach außen anbieten, können bei sicherheitsrelevanten Vorgängen die IP nur über Remote Address ermitteln und dürfen keinen Request-Headern vertrauen;
  2. Webanwendungen, die Nginx oder andere Web-Server als Reverse Proxy verwenden, sollten bei korrekter KonfigurationX-Forwarded-Forden letzten Abschnitt oderX-Real-IPverwenden, um die IP zu ermitteln (weil Remote Address die interne IP des Servers erhält, auf dem Nginx läuft); gleichzeitig sollte der direkte externe Zugriff auf die Webanwendung verboten werden;
  3. In Szenarien, die nicht sicherheitsrelevant sind, wie z. B. der Anzeige des lokalen Wetters anhand der IP, können Sie die IP beziehen ausX-Forwarded-Foreiner früheren Position, aber Sie müssen das IP-Format validieren.

PS: Einige Artikel im Internet schlagen vor, Nginx so zu konfigurieren, aber das ist tatsächlich nicht sinnvoll:

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

Mit dieser Konfiguration wird die Sicherheit zwar verbessert, aber sie löscht auch alle Proxy-Informationen, bevor die Anfrage Nginx erreicht, wodurch es unmöglich wird, Benutzern, die tatsächlich Proxys verwenden, bessere Dienste anzubieten. Sie sollten trotzdem die zugrunde liegenden Prinzipien verstehen und konkrete Szenarien von Fall zu Fall analysieren.

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