PHP $ _SERVER ['HTTP_HOST'] vs. $ _SERVER ['SERVER_NAME'], verstehe ich die Manpages richtig?

167

Ich habe viel gesucht und auch die PHP $ _SERVER-Dokumente gelesen . Habe ich dieses Recht, was ich für meine PHP-Skripte für einfache Linkdefinitionen verwenden soll, die auf meiner Website verwendet werden?

$_SERVER['SERVER_NAME'] basiert auf der Konfigurationsdatei Ihres Webservers (in meinem Fall Apache2) und hängt von einigen Anweisungen ab: (1) VirtualHost, (2) Servername, (3) UseCanonicalName usw.

$_SERVER['HTTP_HOST'] basiert auf der Anfrage des Kunden.

Daher scheint es mir die richtige zu sein, um meine Skripte so kompatibel wie möglich zu machen $_SERVER['HTTP_HOST']. Ist diese Annahme richtig?

Follow-up-Kommentare:

Ich glaube, ich wurde ein wenig paranoid, nachdem ich diesen Artikel gelesen und festgestellt hatte, dass einige Leute sagten "sie würden keinem der $_SERVERVars vertrauen ":

Anscheinend geht es in der Diskussion hauptsächlich darum, $_SERVER['PHP_SELF']warum Sie es nicht im Formularaktionsattribut verwenden sollten, ohne ordnungsgemäß zu entkommen, um XSS-Angriffe zu verhindern.

Meine Schlussfolgerung zu meiner ursprünglichen Frage oben lautet, dass die Verwendung $_SERVER['HTTP_HOST']für alle Links auf einer Website "sicher" ist, ohne dass Sie sich über XSS-Angriffe Gedanken machen müssen, selbst wenn diese in Formularen verwendet werden.

Bitte korrigieren Sie mich, wenn ich falsch liege.

Jeff
quelle

Antworten:

149

Das ist wahrscheinlich der erste Gedanke aller. Aber es ist ein bisschen schwieriger. Siehe Chris Shifletts Artikel SERVER_NAMEVersusHTTP_HOST .

Es scheint, dass es keine Silberkugel gibt. Nur wenn Sie Apache zwingen, den kanonischen Namen zu verwenden, erhalten Sie immer den richtigen Servernamen mit SERVER_NAME.

Entweder machen Sie das oder Sie überprüfen den Hostnamen anhand einer weißen Liste:

$allowed_hosts = array('foo.example.com', 'bar.example.com');
if (!isset($_SERVER['HTTP_HOST']) || !in_array($_SERVER['HTTP_HOST'], $allowed_hosts)) {
    header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request');
    exit;
}
Gumbo
quelle
4
Lol, ich habe diesen Artikel gelesen und er schien meine Frage nicht wirklich zu beantworten. Welches verwenden die Pro-Entwickler? Wenn entweder.
Jeff
2
Interessanterweise wusste ich nie, dass SERVER_NAME die vom Benutzer angegebenen Werte standardmäßig in Apache verwendet.
Powerlord
1
@ Jeff, Für Server, die mehr als eine Sub / Domain hosten, haben Sie nur zwei Möglichkeiten $_SERVER['SERVER_NAME']und $_SERVER['HTTP_HOST'](abgesehen von der Implementierung eines anderen benutzerdefinierten Handshakes basierend auf der Benutzeranforderung). Pro-Entwickler vertrauen den Dingen nicht, die sie nicht vollständig verstehen. So sie entweder ihre SAPI haben Setup richtig perfekt (in diesem Fall die Möglichkeit , sie verwenden wird das richtige Ergebnis geben), oder sie werden so tun , dass Whitelisting es egal, was die SAPI Versorgungswerte.
Pacerier
@Gumbo, Sie müssen den "Port" -Patch aufgrund schwerwiegender Probleme mit bestimmten SAPIs anwenden. Auch array_key_existsist skalierbarer Vergleich zu in_arraydiesem hat O (n) Leistung.
Pacerier
2
@Pacerier array_key_exists und in_array machen verschiedene Dinge, frühere Überprüfungen auf Schlüssel, letztere Werte, sodass Sie sie nicht einfach austauschen können. Wenn Sie ein Array mit zwei Werten haben, sollten Sie sich keine Sorgen um die O (n) -Leistung machen ...
Eis
74

Nur ein zusätzlicher Hinweis: Wenn der Server auf einem anderen Port als 80 ausgeführt wird (wie dies auf einem Entwicklungs- / Intranetcomputer üblich ist), HTTP_HOSTenthält er den Port, während SERVER_NAMEdies nicht der Fall ist.

$_SERVER['HTTP_HOST'] == 'localhost:8080'
$_SERVER['SERVER_NAME'] == 'localhost'

(Zumindest ist mir das bei Apache-Port-basierten virtuellen Hosts aufgefallen.)

Wie Mike unten bemerkt hat, HTTP_HOSTist nicht enthalten , :443wenn auf HTTPS läuft (es sei denn , Sie auf einem Nicht-Standard - Port laufen lassen , die ich nicht getestet).

Simon East
quelle
4
Hinweis: Der Port ist auch in HTTP_HOST für 443 nicht vorhanden (Standard-SSL-Port).
Mike
Mit anderen Worten, der Wert von HTTP_HOSTist nicht genau der Host:Parameter, den der Benutzer angegeben hat. Es basiert lediglich darauf.
Pacerier
1
@Pacerier Nein, dies ist das Gegenteil: HTTP_HOST ist genau das Feld Host:, das mit der HTTP-Anforderung geliefert wurde. Der Port ist Teil davon und Browser erwähnen ihn nicht, wenn es der Standard ist (80 für HTTP; 443 für HTTPS)
xhienne
29

Benutze das eine oder das andere. Sie sind beide gleich (in) sicher, da SERVER_NAME in vielen Fällen ohnehin nur aus HTTP_HOST ausgefüllt wird. Normalerweise gehe ich zu HTTP_HOST, damit der Benutzer auf dem genauen Hostnamen bleibt, auf dem er begonnen hat. Wenn ich beispielsweise dieselbe Site auf einer .com- und .org-Domain habe, möchte ich niemanden von .org an .com senden, insbesondere wenn er möglicherweise Login-Token auf .org hat, die er verlieren würde, wenn er an gesendet wird die andere Domain.

In jedem Fall müssen Sie nur sicherstellen, dass Ihre Webanwendung immer nur für bekanntermaßen gute Domains reagiert. Dies kann entweder (a) mit einer anwendungsseitigen Prüfung wie der von Gumbo oder (b) mit einem virtuellen Host für die gewünschten Domänennamen erfolgen, der nicht auf Anforderungen reagiert , die einen unbekannten Host-Header enthalten.

Der Grund dafür ist, dass Sie, wenn Sie zulassen, dass auf Ihre Site unter einem alten Namen zugegriffen wird, DNS-Rebinding-Angriffen ausgesetzt sind (wenn der Hostname einer anderen Site auf Ihre IP verweist, greift ein Benutzer mit dem Hostnamen des Angreifers und dann mit dem Hostnamen auf Ihre Site zu wird auf die IP des Angreifers verschoben, wobei Ihre Cookies / Authentifizierung mitgenommen werden) und Suchmaschinen-Hijacking (wobei ein Angreifer seinen eigenen Hostnamen auf Ihre Site verweist und versucht, Suchmaschinen dazu zu bringen, ihn als den "besten" primären Hostnamen zu betrachten).

Anscheinend geht es in der Diskussion hauptsächlich um $ _SERVER ['PHP_SELF'] und darum, warum Sie es nicht im Formularaktionsattribut verwenden sollten, ohne ordnungsgemäß zu entkommen, um XSS-Angriffe zu verhindern.

Pfft. Nun, Sie sollten in keinem Attribut etwas verwenden , ohne damit zu entkommen , daher sind Servervariablen dort nichts Besonderes.htmlspecialchars($string, ENT_QUOTES)

Bobince
quelle
Bleiben Sie bei Lösung (a), (b) ist nicht wirklich sicher. Die Verwendung des absoluten URI in HTTP-Anforderungen ermöglicht die Umgehung der Sicherheit von namensbasierten virtuellen Hosts. Die eigentliche Regel lautet also niemals SERVER_NAME oder HTTP_HOST.
Regilero
@ Bobince, Wie funktioniert die erwähnte Suchmaschinenentführung? Suchmaschinen ordnen Wörter Domain- URLs zu , sie befassen sich nicht mit IPs. Warum sagen Sie also, dass "ein Angreifer Suchmaschinen attacker.comals die beste primäre Quelle für die IP Ihres Servers ansehen kann " bedeutet? Für Suchmaschinen scheint das nichts zu bedeuten. Was wird das überhaupt tun?
Pacerier
2
Google hatte (und hat wahrscheinlich immer noch in irgendeiner Form) das Konzept von betrogenen Websites. Wenn Ihre Website als zugänglich http://example.com/ist http://www.example.com/und http://93.184.216.34/diese zu einer Website kombiniert, wählen Sie die beliebteste der Adressen aus und geben Sie nur Links zu dieser zurück Ausführung. Wenn Sie evil-example.comauf dieselbe Adresse zeigen und Google kurz darauf hinweisen könnten, dass Sie als beliebteste Adresse den Saft der Website stehlen könnten. Ich weiß nicht, wie praktisch das heute ist, aber ich habe in der Vergangenheit russische Angreifer von Linkfarmen gesehen, die versucht haben, dies zu tun.
Bobince
24

Dies ist eine ausführliche Übersetzung dessen, was Symfony verwendet, um den Hostnamen zu erhalten ( eine wörtlichere Übersetzung finden Sie im zweiten Beispiel ):

function getHost() {
    $possibleHostSources = array('HTTP_X_FORWARDED_HOST', 'HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR');
    $sourceTransformations = array(
        "HTTP_X_FORWARDED_HOST" => function($value) {
            $elements = explode(',', $value);
            return trim(end($elements));
        }
    );
    $host = '';
    foreach ($possibleHostSources as $source)
    {
        if (!empty($host)) break;
        if (empty($_SERVER[$source])) continue;
        $host = $_SERVER[$source];
        if (array_key_exists($source, $sourceTransformations))
        {
            $host = $sourceTransformations[$source]($host);
        } 
    }

    // Remove port number from host
    $host = preg_replace('/:\d+$/', '', $host);

    return trim($host);
}

Veraltet:

Dies ist meine Übersetzung einer im Symfony-Framework verwendeten Methode in nacktes PHP, die versucht, den Hostnamen in der Reihenfolge der besten Vorgehensweisen auf jede mögliche Weise abzurufen:

function get_host() {
    if ($host = $_SERVER['HTTP_X_FORWARDED_HOST'])
    {
        $elements = explode(',', $host);

        $host = trim(end($elements));
    }
    else
    {
        if (!$host = $_SERVER['HTTP_HOST'])
        {
            if (!$host = $_SERVER['SERVER_NAME'])
            {
                $host = !empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '';
            }
        }
    }

    // Remove port number from host
    $host = preg_replace('/:\d+$/', '', $host);

    return trim($host);
}
antitoxisch
quelle
1
@StefanNch Bitte definieren Sie "diesen Weg".
Showdev
1
@showdev Ich finde es wirklich "schwer", Bedingungsanweisungen wie if ($host = $_SERVER['HTTP_X_FORWARDED_HOST'])oder zu lesen x = a == 1 ? True : False. Als ich es zum ersten Mal sah, suchte mein Gehirn nach einer $ Host-Instanziierung und einer Antwort für "Warum ist nur ein" = "Zeichen?". Ich fange an, schwache Schreibprogrammiersprachen nicht zu mögen. Alles ist anders geschrieben. Sie sparen keine Zeit und sind nichts Besonderes. Ich schreibe keinen Code auf diese Weise, weil ich nach Ablauf der Zeit derjenige bin, der ihn debuggen muss. Sieht für ein müdes Gehirn wirklich chaotisch aus! Ich weiß, dass mein Englisch Englisch ist, aber zumindest versuche ich es.
StefanNch
1
Leute, ich habe einfach den Code von Symfony portiert. So habe ich es genommen. Für alles, was zählt - es funktioniert und es scheint ziemlich gründlich. Ich selbst, auch das ist nicht lesbar genug, aber ich hatte keine Zeit, es komplett neu zu schreiben.
Antitoxikum
2
Sieht gut aus für mich. Dies sind ternäre Operatoren , die bei sachgemäßer Verwendung Zeit (und Bytes) sparen können, ohne die Lesbarkeit zu beeinträchtigen.
Showdev
1
@antitoxic, -1 Symfony-Codierer (wie viele andere auch) wissen nicht genau, was sie in diesem Fall tun. Dies gibt Ihnen nicht den Hostnamen (siehe Simons Antwort). Dies gibt Ihnen nur eine beste Vermutung, die oft falsch sein wird.
Pacerier
11

Ist es "sicher", $_SERVER['HTTP_HOST']alle Links auf einer Site zu verwenden, ohne sich über XSS-Angriffe Gedanken machen zu müssen, selbst wenn sie in Formularen verwendet werden?

Ja, es ist sicher zu verwenden $_SERVER['HTTP_HOST'](und sogar $_GETund $_POST) , solange Sie sie überprüfen, bevor Sie sie akzeptieren. Folgendes mache ich für sichere Produktionsserver:

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
$reject_request = true;
if(array_key_exists('HTTP_HOST', $_SERVER)){
    $host_name = $_SERVER['HTTP_HOST'];
    // [ need to cater for `host:port` since some "buggy" SAPI(s) have been known to return the port too, see http://goo.gl/bFrbCO
    $strpos = strpos($host_name, ':');
    if($strpos !== false){
        $host_name = substr($host_name, $strpos);
    }
    // ]
    // [ for dynamic verification, replace this chunk with db/file/curl queries
    $reject_request = !array_key_exists($host_name, array(
        'a.com' => null,
        'a.a.com' => null,
        'b.com' => null,
        'b.b.com' => null
    ));
    // ]
}
if($reject_request){
    // log errors
    // display errors (optional)
    exit;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
echo 'Hello World!';
// ...

Der Vorteil von $_SERVER['HTTP_HOST']ist, dass sein Verhalten klarer definiert ist als $_SERVER['SERVER_NAME']. Kontrast ➫➫ :

Inhalt des Hosts: Header aus der aktuellen Anforderung, falls vorhanden.

mit:

Der Name des Serverhosts, unter dem das aktuelle Skript ausgeführt wird.

Die Verwendung einer besser definierten Schnittstelle wie $_SERVER['HTTP_HOST']bedeutet, dass mehr SAPIs diese mithilfe eines zuverlässigen, genau definierten Verhaltens implementieren . (Im Gegensatz zu den anderen .) Es ist jedoch immer noch vollständig SAPI-abhängig ➫➫ :

Es gibt keine Garantie dafür, dass jeder Webserver diese [ $_SERVEREinträge] bereitstellt . Server können einige weglassen oder andere bereitstellen, die hier nicht aufgeführt sind.

Um zu verstehen, wie der Hostname ordnungsgemäß abgerufen wird, müssen Sie in erster Linie verstehen, dass ein Server, der nur Code enthält, keine Möglichkeit hat, seinen eigenen Namen im Netzwerk zu kennen (Voraussetzung für die Überprüfung) . Es muss mit einer Komponente verbunden sein, die ihm einen eigenen Namen gibt. Dies kann erfolgen über:

  • lokale Konfigurationsdatei

  • lokale Datenbank

  • fest codierter Quellcode

  • externe Anfrage ( Curl )

  • Client / Angreifer- Host:Anfrage

  • etc

Normalerweise erfolgt dies über die lokale Konfigurationsdatei (SAPI). Beachten Sie, dass Sie es richtig konfiguriert haben, z. B. in Apache ➫➫ :

Einige Dinge müssen gefälscht werden, damit der dynamische virtuelle Host wie ein normaler aussieht.

Der wichtigste ist der Servername, der von Apache zum Generieren von selbstreferenziellen URLs usw. verwendet wird. Er wird mit der ServerNameDirektive konfiguriert und steht CGIs über die SERVER_NAMEUmgebungsvariable zur Verfügung.

Der zur Laufzeit verwendete tatsächliche Wert wird durch die Einstellung UseCanonicalName gesteuert .

Mit UseCanonicalName Off dem Server - Name kommt von dem Inhalt des Host:Header in der Anfrage. Damit UseCanonicalName DNS kommt eine Reverse-DNS-Suche der IP-Adresse des virtuellen Hosts zustande. Die erstere Einstellung wird für namenbasiertes dynamisches virtuelles Hosting verwendet, und die letztere Einstellung wird für ** IP-basiertes Hosting verwendet.

Wenn Apache kann nicht den Namen des Servers arbeiten, weil es keine Host:Header oder der DNS - Lookup fehlschlägt dann den Wert konfiguriert ServerNamestattdessen verwendet wird.

Pacerier
quelle
8

Der Hauptunterschied zwischen beiden besteht darin, dass $_SERVER['SERVER_NAME']es sich um eine servergesteuerte Variable und $_SERVER['HTTP_HOST']einen benutzergesteuerten Wert handelt.

Die Faustregel lautet, niemals den Werten des Benutzers zu vertrauen, daher $_SERVER['SERVER_NAME']ist dies die bessere Wahl.

Wie Gumbo betonte, erstellt Apache SERVER_NAME aus vom Benutzer angegebenen Werten, wenn Sie dies nicht festlegen UseCanonicalName On.

Bearbeiten: Wenn die Site einen namenbasierten virtuellen Host verwendet, ist der HTTP-Host-Header der einzige Weg, um Sites zu erreichen, die nicht die Standard-Site sind.

Powerlord
quelle
Verstanden. Mein Hangup lautet: "Wie kann ein Benutzer den Wert von $ _SERVER ['HTTP_HOST'] ändern?" Ist es überhaupt möglich?
Jeff
5
Ein Benutzer kann dies ändern, da es sich nur um den Inhalt des Host-Headers aus der eingehenden Anforderung handelt. Der Hauptserver (oder der an Standard : 80 gebundene VirtualHost ) antwortet auf alle unbekannten Hosts, sodass der Inhalt des Host-Tags auf dieser Site auf einen beliebigen Wert festgelegt werden kann.
Powerlord
4
Beachten Sie, dass IP-basierte virtuelle Hosts IMMER auf ihre spezifische IP reagieren, sodass Sie dem HTTP-Host-Wert unter keinen Umständen vertrauen können.
Powerlord
1
@ Jeff, es ist wie zu fragen "Es ist möglich, die Telefonnummer der Pizzahütte anzurufen und um ein Gespräch mit KFC-Mitarbeitern zu bitten ?" Natürlich können Sie alles anfordern, was Sie wollen. @Powerlord, Dies hat nichts mit IP-basierten virtuellen Hosts zu tun. Ihr Server kann unabhängig vom IP-basierten virtuellen Host unter keinen Umständen dem HTTP- Host:Wert vertrauen , es sei denn, Sie haben ihn bereits manuell oder über das Setup Ihres SAPI überprüft .
Pacerier
3

Ich bin mir nicht sicher und vertraue nicht wirklich, $_SERVER['HTTP_HOST']da dies vom Header des Clients abhängt. Auf andere Weise, wenn eine vom Client angeforderte Domäne nicht meine ist, werden sie nicht auf meine Site gelangen, da das DNS- und das TCP / IP-Protokoll sie auf das richtige Ziel verweisen. Ich weiß jedoch nicht, ob es möglich ist, den DNS-, Netzwerk- oder sogar Apache-Server zu entführen. Um sicher zu gehen, definiere ich den Hostnamen in der Umgebung und vergleiche ihn mit$_SERVER['HTTP_HOST'] .

Fügen Sie die SetEnv MyHost domain.com.htaccess-Datei im Stammverzeichnis hinzu und fügen Sie diesen Code in Common.php hinzu

if (getenv('MyHost')!=$_SERVER['HTTP_HOST']) {
  header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request');
  exit();
}

Ich füge diese Common.php-Datei in jede PHP-Seite ein. Diese Seite führt alles aus, was für jede Anforderung erforderlich ist, z. B. session_start()Sitzungscookie ändern und ablehnen, wenn die Post-Methode aus einer anderen Domäne stammt.

CallMeLaNN
quelle
1
Natürlich ist es möglich, DNS zu umgehen. Ein Angreifer kann einfach einen fradulenten Host:Wert direkt an die IP Ihres Servers ausgeben .
Pacerier
1

XSSwird immer da sein, auch wenn Sie verwenden $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']ODER$_SERVER['PHP_SELF']

Jaydeep Dave
quelle
1

Zunächst möchte ich mich bei Ihnen für all die guten Antworten und Erklärungen bedanken. Dies ist die Methode, die ich basierend auf all Ihren Antworten erstellt habe, um die Basis-URL zu erhalten. Ich benutze es nur in sehr seltenen Situationen. Daher liegt der Schwerpunkt NICHT auf Sicherheitsproblemen wie XSS-Angriffen. Vielleicht braucht es jemand.

// Get base url
function getBaseUrl($array=false) {
    $protocol = "";
    $host = "";
    $port = "";
    $dir = "";  

    // Get protocol
    if(array_key_exists("HTTPS", $_SERVER) && $_SERVER["HTTPS"] != "") {
        if($_SERVER["HTTPS"] == "on") { $protocol = "https"; }
        else { $protocol = "http"; }
    } elseif(array_key_exists("REQUEST_SCHEME", $_SERVER) && $_SERVER["REQUEST_SCHEME"] != "") { $protocol = $_SERVER["REQUEST_SCHEME"]; }

    // Get host
    if(array_key_exists("HTTP_X_FORWARDED_HOST", $_SERVER) && $_SERVER["HTTP_X_FORWARDED_HOST"] != "") { $host = trim(end(explode(',', $_SERVER["HTTP_X_FORWARDED_HOST"]))); }
    elseif(array_key_exists("SERVER_NAME", $_SERVER) && $_SERVER["SERVER_NAME"] != "") { $host = $_SERVER["SERVER_NAME"]; }
    elseif(array_key_exists("HTTP_HOST", $_SERVER) && $_SERVER["HTTP_HOST"] != "") { $host = $_SERVER["HTTP_HOST"]; }
    elseif(array_key_exists("SERVER_ADDR", $_SERVER) && $_SERVER["SERVER_ADDR"] != "") { $host = $_SERVER["SERVER_ADDR"]; }
    //elseif(array_key_exists("SSL_TLS_SNI", $_SERVER) && $_SERVER["SSL_TLS_SNI"] != "") { $host = $_SERVER["SSL_TLS_SNI"]; }

    // Get port
    if(array_key_exists("SERVER_PORT", $_SERVER) && $_SERVER["SERVER_PORT"] != "") { $port = $_SERVER["SERVER_PORT"]; }
    elseif(stripos($host, ":") !== false) { $port = substr($host, (stripos($host, ":")+1)); }
    // Remove port from host
    $host = preg_replace("/:\d+$/", "", $host);

    // Get dir
    if(array_key_exists("SCRIPT_NAME", $_SERVER) && $_SERVER["SCRIPT_NAME"] != "") { $dir = $_SERVER["SCRIPT_NAME"]; }
    elseif(array_key_exists("PHP_SELF", $_SERVER) && $_SERVER["PHP_SELF"] != "") { $dir = $_SERVER["PHP_SELF"]; }
    elseif(array_key_exists("REQUEST_URI", $_SERVER) && $_SERVER["REQUEST_URI"] != "") { $dir = $_SERVER["REQUEST_URI"]; }
    // Shorten to main dir
    if(stripos($dir, "/") !== false) { $dir = substr($dir, 0, (strripos($dir, "/")+1)); }

    // Create return value
    if(!$array) {
        if($port == "80" || $port == "443" || $port == "") { $port = ""; }
        else { $port = ":".$port; } 
        return htmlspecialchars($protocol."://".$host.$port.$dir, ENT_QUOTES); 
    } else { return ["protocol" => $protocol, "host" => $host, "port" => $port, "dir" => $dir]; }
}
Mike
quelle