web-dev-qa-db-fra.com

PHP $ _SERVER ['HTTP_Host'] vs $ _SERVER ['SERVER_NAME'], ai-je bien compris les pages de manuel?

J'ai fait beaucoup de recherches et j'ai également lu le PHP $ _ document SERVER . Ai-je le droit de choisir mes scripts PHP pour les définitions de liens simples utilisées sur l'ensemble de mon site?

$_SERVER['SERVER_NAME'] est basé sur le fichier de configuration de votre serveur Web (Apache2 dans mon cas), et varie en fonction de quelques directives: (1) VirtualHost, (2) ServerName, (3) UseCanonicalName, etc.

$_SERVER['HTTP_Host'] est basé sur la demande du client.

Par conséquent, il me semble que le meilleur à utiliser pour rendre mes scripts aussi compatibles que possible serait $_SERVER['HTTP_Host']. Cette hypothèse est-elle correcte?

Commentaires suivants:

Je suppose que je suis devenu un peu paranoïaque après avoir lu cet article et noté que certaines personnes disaient "elles ne feraient confiance à aucun des $_SERVER vars":

Apparemment, la discussion porte principalement sur $_SERVER['PHP_SELF'] et sur la raison pour laquelle vous ne devriez pas l'utiliser dans l'attribut d'action de formulaire sans l'échappement approprié pour empêcher les attaques XSS.

Ma conclusion à propos de la question initiale ci-dessus est qu'il est "sûr" d'utiliser $_SERVER['HTTP_Host'] pour tous les liens d'un site sans s'inquiéter des attaques XSS, même lorsqu'ils sont utilisés dans des formulaires.

Corrigez-moi si j'ai tort, s'il-vous plait.

150
Jeff

C’est probablement la première pensée de tout le monde. Mais c’est un peu plus difficile. Voir article de Chris Shiflett SERVER_NAME_ OU _HTTP_Host .

Il semble qu'il n'y ait pas de solution miracle. Ce n'est que lorsque vous forcez Apache à utiliser le nom canonique que vous obtiendrez toujours le nom de serveur correct avec _SERVER_NAME_.

Donc, soit vous y allez, soit vous comparez le nom d'hôte à une liste blanche:

_$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

Juste une remarque supplémentaire - si le serveur fonctionne sur un port autre que 80 (comme cela pourrait être le cas sur un ordinateur de développement/intranet), alors HTTP_Host contient le port, alors que SERVER_NAME ne le fait pas.

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

(Du moins c'est ce que j'ai remarqué dans les hôtes virtuels basés sur le port Apache)

Comme Mike l'a noté ci-dessous, HTTP_Host contient pas contient :443 lorsqu'il est exécuté sur HTTPS (sauf si vous utilisez un port non standard, que je n'ai pas testé). .

63
Simon East

Utilisez soit. Ils sont tous deux également (in) sécurisés, car dans de nombreux cas, SERVER_NAME est de toute façon renseigné à partir de HTTP_Host. Je choisis normalement HTTP_Host, afin que l'utilisateur reste sur le nom d'hôte exact sur lequel il a démarré. Par exemple, si j'ai le même site sur un domaine .com et .org, je ne souhaite pas envoyer quelqu'un de .org à .com, surtout s'ils peuvent avoir des jetons de connexion sur .org qu'ils perdraient s'ils étaient envoyés à. l'autre domaine.

Quoi qu'il en soit, vous devez simplement vous assurer que votre application Web ne répondra que pour les domaines connus. Cela peut être fait soit (a) avec une vérification côté application comme celle de Gumbo, soit (b) en utilisant un hôte virtuel sur le (s) nom (s) de domaine que vous voulez ne répond pas aux demandes qui donnent un en-tête d'hôte inconnu.

La raison en est que si vous autorisez l'accès à votre site sous n'importe quel ancien nom, vous vous exposez à des attaques par réattribution DNS (lorsque le nom d'hôte d'un autre site pointe vers votre IP, un utilisateur accède à votre site avec le nom d'hôte de l'attaquant, puis le nom d'hôte. est déplacé vers l'adresse IP de l'attaquant, en prenant vos cookies/auth avec lui) et le détournement de moteur de recherche (lorsqu'un attaquant pointe son propre nom d'hôte sur votre site et tente de faire en sorte que les moteurs de recherche le voient comme le "meilleur" nom d'hôte principal).

Apparemment, la discussion porte principalement sur $ _SERVER ['PHP_SELF'] et sur la raison pour laquelle vous ne devriez pas l'utiliser dans l'attribut d'action de formulaire sans l'échappement approprié pour empêcher les attaques XSS.

Pfft. Eh bien, vous ne devriez pas utiliser quoi que ce soit dans aucun attribut sans échapper à htmlspecialchars($string, ENT_QUOTES), il n'y a donc rien de particulier à propos des variables de serveur.

25
bobince

Ceci est une traduction commentée de ce que Symfony utilise pour obtenir le nom d'hôte (. Voir le deuxième exemple pour une traduction plus littérale ):

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

obsolète:

Ceci est ma traduction à nu PHP d'une méthode utilisée dans le framework Symfony qui tente d'obtenir le nom d'hôte de toutes les manières possibles dans l'ordre des meilleures pratiques:

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

Est-il "sûr" d'utiliser $_SERVER['HTTP_Host'] pour tous les liens d'un site sans s'inquiéter des attaques XSS, même lorsqu'ils sont utilisés dans des formulaires?

Oui, il est sûr d'utiliser $_SERVER['HTTP_Host'], (et même $_GET et $_POST) tant que vous les vérifiez avant de les accepter. Voici ce que je fais pour les serveurs de production sécurisés:

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
$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!';
// ...

L'avantage de $_SERVER['HTTP_Host'] est que son comportement est plus défini que $_SERVER['SERVER_NAME']. Contraste ➫➫ :

Contenu de l'hôte: en-tête de la demande en cours, le cas échéant.

avec:

Nom de l'hôte du serveur sous lequel le script actuel est en cours d'exécution.

Utiliser une interface mieux définie comme $_SERVER['HTTP_Host'] signifie que davantage de SAPI l'implémenteront en utilisant un comportement fiable bien défini. (Contrairement à l'autre .) Cependant, il reste totalement dépendant de SAPI :

Rien ne garantit que chaque serveur Web fournira l'une de ces [$_SERVER entrées]; les serveurs peuvent en omettre ou en fournir d’autres qui ne sont pas listés ici.

Pour comprendre comment récupérer correctement le nom d’hôte, vous devez avant tout comprendre qu’un serveur qui ne contient que du code n’a aucun moyen de le savoir (condition préalable à la vérification) son propre nom sur le réseau. Il doit s’interfacer avec un composant qui lui fournit son propre nom. Cela peut être fait via:

  • fichier de configuration local

  • base de données locale

  • code source codé en dur

  • demande externe ( curl )

  • demande Host: du client/attaquant

  • etc

Cela se fait généralement via le fichier de configuration local (SAPI). Notez que vous l'avez configuré correctement, par exemple. dans Apache ➫➫ :

Un certain nombre de choses doivent être simulées pour que l’hôte virtuel dynamique ressemble à un hôte normal.

Le plus important est le nom du serveur utilisé par Apache pour générer des URL auto-référentielles, etc. Il est configuré avec la directive ServerName et il est disponible pour les CGI via la variable d'environnement SERVER_NAME.

La valeur réelle utilisée au moment de l'exécution est contrôlée par avec le paramètre UseCanonicalName.

Avec UseCanonicalName Off le nom du serveur provient du contenu de l'en-tête Host: de la requête. Avec UseCanonicalName DNS, cela provient d'une recherche DNS inversée de l'adresse IP de l'hôte virtuel. Le premier paramètre est utilisé pour l'hébergement virtuel dynamique basé sur le nom, et le second pour l'hébergement basé sur IP **.

Si Apache ne peut pas résoudre le nom du serveur car il n'y a pas d'en-tête Host: ou la recherche DNS échoue alors la valeur configurée avec ServerName est utilisée à la place.

9
Pacerier

La principale différence entre les deux réside dans le fait que $_SERVER['SERVER_NAME'] est une variable contrôlée par le serveur, tandis que $_SERVER['HTTP_Host'] est une valeur contrôlée par l'utilisateur.

La règle de base est de ne jamais faire confiance aux valeurs de l'utilisateur, donc $_SERVER['SERVER_NAME'] est le meilleur choix.

Comme Gumbo l'a souligné, Apache construira SERVER_NAME à partir de valeurs fournies par l'utilisateur si vous ne définissez pas UseCanonicalName On.

Édition: Cela dit, si le site utilise un hôte virtuel basé sur un nom, l’en-tête de l’hôte HTTP est le seul moyen d’atteindre des sites qui ne sont pas le site par défaut.

8
Powerlord

Je ne suis pas sûr et je ne fais pas vraiment confiance à $_SERVER['HTTP_Host'] parce que cela dépend de l'en-tête du client. D'une autre manière, si un domaine demandé par le client n'est pas le mien, ils ne pourront pas accéder à mon site car les protocoles DNS et TCP/IP le dirigent vers la bonne destination. Cependant, je ne sais pas s'il est possible de pirater le serveur DNS, le réseau ou même Apache. Pour plus de sécurité, je définis le nom d’hôte dans l’environnement et le compare à $_SERVER['HTTP_Host'].

Ajoutez SetEnv MyHost domain.com dans le fichier .htaccess à la racine et ajoutez ce code dans Common.php

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

J'inclus ce fichier Common.php dans chaque page php. Cette page fait tout ce qui est nécessaire pour chaque requête, comme session_start(), modifie le cookie de session et rejette si la méthode de publication provient d'un domaine différent.

2
CallMeLaNN

XSS sera toujours là même si vous utilisez $_SERVER['HTTP_Host'], $_SERVER['SERVER_NAME'] OR $_SERVER['PHP_SELF']

1
Jaydeep Dave

Je tiens tout d'abord à vous remercier pour toutes les bonnes réponses et explications. C’est la méthode que j’ai créée sur la base de toutes vos réponses pour obtenir l’URL de base. Je ne l'utilise que dans de très rares situations. Donc, il n'y a pas une grande concentration sur les problèmes de sécurité, comme les attaques XSS. Peut-être que quelqu'un en a besoin.

// 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