wake-up-neo.net

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

Ich habe viel gesucht und auch die PHP $ _ SERVER docs gelesen. Habe ich dieses Recht, was ich für meine PHP Skripte für einfache Linkdefinitionen verwenden soll, die auf meiner Site 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, dass die richtige Option, um meine Skripte so kompatibel wie möglich zu machen, $_SERVER['HTTP_Host'] Ist. Ist diese Annahme richtig?

Nachfolgende Kommentare:

Ich schätze, ich wurde ein wenig paranoid, nachdem ich diesen Artikel gelesen hatte und festgestellt habe, dass einige Leute sagten "sie würden keinem der $_SERVER - Vars vertrauen":

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

Meine Schlussfolgerung zu meiner ursprünglichen Frage oben lautet, dass es "sicher" ist, $_SERVER['HTTP_Host'] Für alle Links auf einer Site zu verwenden, ohne sich um XSS-Angriffe sorgen zu müssen, selbst wenn sie in Formularen verwendet werden.

Bitte korrigieren Sie mich, falls ich falsch liege.

150
Jeff

Das ist wahrscheinlich der erste Gedanke von allen. Aber es ist etwas schwieriger. Siehe Chris Shifletts Artikel SERVER_NAME Gegen HTTP_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 vergleichen den Hostnamen mit 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;
}
139
Gumbo

Nur eine zusätzliche Anmerkung - wenn der Server an einem anderen Port als 80 ausgeführt wird (wie es auf einem Entwicklungs-/Intranet-Computer üblich ist), dann HTTP_Host enthält den Port, während SERVER_NAME nicht.

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

(Zumindest ist mir das bei portbasierten Apache-Virtualhosts aufgefallen.)

Wie Mike unten bemerkt hat, HTTP_Host enthält nicht:443 bei Ausführung mit HTTPS (es sei denn, Sie verwenden einen nicht standardmäßigen Port, den ich nicht getestet habe).

63
Simon East

Benutze das eine oder das andere. Sie sind beide gleichermaßen (in) sicher, da SERVER_NAME in vielen Fällen ohnehin nur von HTTP_Host ausgefüllt wird. Normalerweise wähle ich HTTP_Host, damit der Benutzer den genauen Hostnamen beibehält, auf dem er gestartet ist. Wenn ich zum Beispiel dieselbe Site auf einer .com- und .org-Domain habe, möchte ich niemanden von .org nach .com senden, insbesondere nicht, wenn er Anmeldetoken auf .org hat, die er verlieren würde, wenn er an gesendet würde die andere Domain.

In jedem Fall müssen Sie nur sicherstellen, dass Ihre Webanwendung immer nur auf als funktionierend bekannte Domains reagiert. Dies kann entweder (a) mit einer anwendungsseitigen Prüfung wie Gumbo's oder (b) durch Verwendung eines virtuellen Hosts für die Domain-Namen erfolgen, die antwortet nicht auf Anfragen, die geben ein unbekannter Host-Header.

Der Grund dafür ist, dass Sie sich für DNS-Rebinding-Angriffe offen legen, wenn Sie zulassen, dass auf Ihre Site unter einem alten Namen zugegriffen wird (wenn der Hostname einer anderen Site auf Ihre IP verweist, greift ein Benutzer mit dem Hostnamen des Angreifers auf Ihre Site zu, dann mit dem Hostnamen wird auf die IP des Angreifers verschoben, nimmt Ihre Cookies/Authentifizierung mit) und entführt Suchmaschinen (wobei ein Angreifer seinen eigenen Hostnamen auf Ihre Site zeigt und versucht, Suchmaschinen zu veranlassen, diesen 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 Attribut form action verwenden sollten, ohne ordnungsgemäß zu maskieren, um XSS-Angriffe zu verhindern.

Pfft. Nun, Sie sollten in keinem Attribut etwas verwenden, ohne mit htmlspecialchars($string, ENT_QUOTES) zu entkommen. Es gibt also nichts Besonderes an Servervariablen.

25
bobince

Dies ist eine ausführliche Übersetzung dessen, was Symfony verwendet, um den Host-Namen abzurufen (. Eine wörtliche Ü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 PHP einer in Symfony Framework verwendeten Methode, die versucht, den Hostnamen in der Reihenfolge der Best Practices 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);
}
24
antitoxic

Ist es "sicher", $_SERVER['HTTP_Host'] Für alle Links auf einer Site zu verwenden, ohne sich um XSS-Angriffe sorgen zu müssen, selbst wenn diese in Formularen verwendet werden?

Ja, es ist sicher , $_SERVER['HTTP_Host'] (Und sogar $_GET Und $_POST) zu verwenden, solange Sie dies bestätigen bevor Sie sie annehmen. Das 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 das von $_SERVER['SERVER_NAME']. Kontrast ➫➫ :

Inhalt des Hosts: Header der aktuellen Anfrage, falls vorhanden.

mit:

Der Name des Server-Hosts, unter dem das aktuelle Skript ausgeführt wird.

Die Verwendung einer besser definierten Schnittstelle wie $_SERVER['HTTP_Host'] Bedeutet, dass mehr SAPIs sie mit zuverlässigem genau definiertem Verhalten implementieren. (Im Gegensatz zu the other .) Es ist jedoch immer noch vollständig SAPI-abhängig ➫➫ :

Es kann nicht garantiert werden, dass jeder Webserver einen dieser [$_SERVER - Einträge] bereitstellt. Server können einige auslassen oder andere bereitstellen, die hier nicht aufgeführt sind.

Um zu verstehen, wie der Hostname richtig abgerufen werden kann, müssen Sie in erster Linie verstehen, dass ein Server, der nur code enthält, keine Mittel zum Wissen hat (Voraussetzung für die Überprüfung) einen eigenen Namen im Netzwerk. Es muss mit einer Komponente verbunden sein, die einen eigenen Namen liefert. Dies kann erfolgen über:

  • lokale Konfigurationsdatei

  • lokale Datenbank

  • fest codierter Quellcode

  • externe Anfrage ( locken )

  • client/Angreifer Host: Anfrage

  • usw

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

Ein paar Dinge müssen vorgetäuscht werden, damit der dynamische virtuelle Host wie ein normaler aussieht.

Der wichtigste ist der Servername, der von Apache zum Generieren selbstreferenzieller URLs usw. verwendet wird. Er wird mit der Direktive ServerName konfiguriert und steht CGIs über die Umgebungsvariable SERVER_NAME Zur Verfügung .

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

Bei UseCanonicalName Off Stammt der Servername aus dem Inhalt des Headers Host: In der Anfrage. Mit UseCanonicalName DNS Wird eine umgekehrte DNS-Suche der IP-Adresse des virtuellen Hosts durchgeführt. Die erstere Einstellung wird für namenbasiertes dynamisches virtuelles Hosting und die letztere für ** IP-basiertes Hosting verwendet.

Wenn Apache den Servernamen nicht ermitteln kann, weil kein Host: - Header vorhanden ist oder die DNS-Suche fehlschlägt , dann Stattdessen wird der mit ServerName konfigurierte Wert verwendet.

9
Pacerier

Der Hauptunterschied zwischen den beiden besteht darin, dass $_SERVER['SERVER_NAME'] Eine servergesteuerte Variable ist, während $_SERVER['HTTP_Host'] Ein benutzergesteuerter Wert ist.

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

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

Bearbeiten: Abgesehen davon ist der HTTP-Host-Header die einzige Möglichkeit, Websites zu erreichen, die nicht die Standardwebsite sind, wenn die Website einen namensbasierten virtuellen Host verwendet.

8
Powerlord

Ich bin mir nicht sicher und vertraue nicht wirklich $_SERVER['HTTP_Host'], Da dies vom Header des Clients abhängt. Wenn eine vom Client angeforderte Domain nicht meine Domain ist, gelangen sie nicht in meine Site, da 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 den Apache-Server zu hijacken. Um sicher zu gehen, definiere ich den Hostnamen in der Umgebung und vergleiche ihn mit $_SERVER['HTTP_Host'].

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

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 tut alles, was für jede Anfrage erforderlich ist, wie session_start(), ändert das Sitzungs-Cookie und lehnt ab, wenn die Post-Methode von einer anderen Domain stammt.

2
CallMeLaNN

XSS wird immer da sein, auch wenn Sie $_SERVER['HTTP_Host'], $_SERVER['SERVER_NAME'] OR $_SERVER['PHP_SELF']

1
Jaydeep Dave

Zunächst möchte ich mich für all die guten Antworten und Erklärungen bedanken. Dies ist die Methode, die ich basierend auf Ihrer Antwort erstellt habe, um die Basis-URL zu erhalten. Ich benutze es nur in sehr seltenen Situationen. Daher liegt der Schwerpunkt NICHT auf Sicherheitsaspekten 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]; }
}
0
Mike