PHP $ _SERVER ['HTTP_HOST'] vs. $ _SERVER ['SERVER_NAME'], sto capendo correttamente le pagine man?


167

Ho fatto molte ricerche e ho anche letto i documenti PHP $ _SERVER . Ho questo diritto su quale utilizzare per i miei script PHP per semplici definizioni di link utilizzate in tutto il mio sito?

$_SERVER['SERVER_NAME'] si basa sul file di configurazione del tuo server web (Apache2 nel mio caso) e varia a seconda di alcune direttive: (1) VirtualHost, (2) ServerName, (3) UseCanonicalName, ecc.

$_SERVER['HTTP_HOST'] si basa sulla richiesta del client.

Pertanto, mi sembrerebbe quello giusto da usare per rendere i miei script il più compatibili possibile $_SERVER['HTTP_HOST']. Questo assunto è corretto?

Commenti di follow-up:

Immagino di essere diventato un po 'paranoico dopo aver letto questo articolo e notato che alcune persone hanno detto "non si sarebbero fidati di nessuno dei vari $_SERVER":

Apparentemente la discussione riguarda principalmente $_SERVER['PHP_SELF']e perché non dovresti usarlo nell'attributo action form senza una corretta escape per prevenire attacchi XSS.

La mia conclusione sulla mia domanda originale sopra è che è "sicuro" da usare $_SERVER['HTTP_HOST']per tutti i collegamenti in un sito senza doversi preoccupare degli attacchi XSS, anche se usati nei moduli.

Per favore correggimi se sbaglio.

Risposte:


149

Questo è probabilmente il primo pensiero di tutti. Ma è un po 'più difficile. Vedi l'articolo di Chris Shiflett SERVER_NAMEcontroHTTP_HOST .

Sembra che non ci siano proiettili d'argento. Solo quando imponi ad Apache di usare il nome canonico otterrai sempre il nome del server giusto SERVER_NAME.

Quindi o vai con quello o controlli il nome host su una lista bianca:

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

4
Lol, ho letto quell'articolo e non sembrava proprio rispondere alla mia domanda. Quale usano gli sviluppatori professionisti? Se uno dei due.
Jeff,

2
Per quanto interessante, non ho mai saputo che SERVER_NAME usasse i valori forniti dall'utente per impostazione predefinita in Apache.
Powerlord,

1
@Jeff, per i server che ospitano più di un sottodominio / dominio, hai solo due scelte $_SERVER['SERVER_NAME']e $_SERVER['HTTP_HOST'](oltre all'implementazione di qualche altra stretta di mano personalizzata basata sulla richiesta dell'utente). Gli sviluppatori professionisti non si fidano delle cose che non comprendono completamente. Così essi o hanno la loro SAPI perfettamente impostato correttamente (in tal caso l'opzione che usano vi darà il risultato corretto), o lo faranno whitelist in modo tale che non importa quali valori la fornitura SAPI.
Pacerier

@Gumbo, è necessario applicare la patch "port" a causa di gravi problemi con determinate SAPI. Inoltre, array_key_existsè più scalabile rispetto a in_arrayquello con prestazioni O (n).
Pacerier

2
@Pacerier array_key_exists e in_array fanno cose diverse, prima controlla le chiavi, ultimi valori, quindi non puoi semplicemente scambiarli. Inoltre, se hai una matrice di due valori, non dovresti davvero preoccuparti delle prestazioni di O (n) ...
eis

74

Solo una nota aggiuntiva: se il server funziona su una porta diversa da 80 (come potrebbe essere comune su una macchina di sviluppo / intranet), allora HTTP_HOSTcontiene la porta, mentre SERVER_NAMEnon lo è.

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

(Almeno questo è quello che ho notato nei virtualhost basati su porte Apache)

Come Mike ha indicato di seguito, HTTP_HOSTnon senza contenere :443se in esecuzione su HTTPS (a meno che non si sta eseguendo su una porta non standard, che non ho ancora testato).


4
Nota: la porta non è presente nemmeno in HTTP_HOST per 443 (porta SSL predefinita).
Mike,

Quindi, in altre parole, il valore di HTTP_HOSTnon è esattamente il Host:parametro fornito dall'utente. Si basa semplicemente su quello.
Pacerier

1
@Pacerier No, questo è l'opposto: HTTP_HOST è esattamente il campo Host: che è stato fornito con la richiesta HTTP. La porta ne fa parte e i browser non la menzionano quando è quella predefinita (80 per HTTP; 443 per HTTPS)
xhienne

29

Usa entrambi. Sono entrambi ugualmente (in) sicuri, poiché in molti casi SERVER_NAME è comunque popolato da HTTP_HOST comunque. Normalmente scelgo HTTP_HOST, in modo che l'utente rimanga sul nome host esatto da cui ha iniziato. Ad esempio, se ho lo stesso sito su un dominio .com e .org, non desidero inviare qualcuno da .org a .com, in particolare se potrebbero avere token di accesso su .org che perderebbero se inviati a l'altro dominio.

Ad ogni modo, devi solo essere sicuro che la tua webapp risponderà sempre e solo per domini noti. Questo può essere fatto (a) con un controllo sul lato applicazione come quello di Gumbo, oppure (b) usando un host virtuale sul nome o sui nomi di dominio desiderati che non rispondono alle richieste che forniscono un'intestazione host sconosciuta.

La ragione di ciò è che se consenti al tuo sito di accedere con un vecchio nome, ti apri agli attacchi di rebinding DNS (in cui il nome host di un altro sito punta al tuo IP, un utente accede al tuo sito con il nome host dell'attaccante, quindi il nome host viene spostato sull'IP dell'attaccante, portando i tuoi cookie / auth con esso) e il dirottamento dei motori di ricerca (in cui un utente malintenzionato punta il proprio nome host sul tuo sito e cerca di far sì che i motori di ricerca lo vedano come il "miglior" nome host primario).

Apparentemente la discussione riguarda principalmente $ _SERVER ['PHP_SELF'] e il motivo per cui non dovresti usarlo nell'attributo action form senza una corretta escape per prevenire attacchi XSS.

Pfft. Beh, non dovresti usare nulla in nessun attributo senza scappare htmlspecialchars($string, ENT_QUOTES), quindi non c'è niente di speciale nelle variabili del server.


Rimanere con la soluzione (a), (b) non è davvero sicuro, l'utilizzo dell'URI assoluto nelle richieste HTTP consente di ignorare la sicurezza degli host virtuali basati sul nome. Quindi la vera regola non è mai affidabile SERVER_NAME o HTTP_HOST.
regilero,

@bobince, come funziona il menzionato dirottamento del motore di ricerca? I motori di ricerca mappano le parole agli URL di dominio , non gestiscono gli IP. Quindi perché dici che "un utente malintenzionato può far sì che i motori di ricerca considerino attacker.comla migliore fonte primaria per l'IP del tuo server"? Ciò non sembra significare nulla per i motori di ricerca, che cosa farà?
Pacerier

2
Google certamente aveva (e probabilmente ha ancora in qualche modo) il concetto di siti duplicati, in modo che se il tuo sito è accessibile come http://example.com/, http://www.example.com/e http://93.184.216.34/li combinerebbe in un sito, scegli il più popolare degli indirizzi e restituire solo i collegamenti a quello versione. Se potessi indicare evil-example.comlo stesso indirizzo e fare in modo che Google veda brevemente come l'indirizzo più popolare potresti rubare il succo del sito. Non so quanto sia pratico oggi, ma ho visto attaccanti russi della farm di collegamento in passato tentare di farlo in passato.
Bobince

24

Questa è una traduzione dettagliata di ciò che Symfony usa per ottenere il nome host ( vedi il secondo esempio per una traduzione più letterale ):

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

superata:

Questa è la mia traduzione in nudo PHP di un metodo usato nel framework Symfony che cerca di ottenere il nome host da ogni modo possibile in ordine di buone pratiche:

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

1
@StefanNch Definisci "in questo modo".
showdev,

1
@showdev Trovo davvero "difficile" leggere un'istruzione di condizione come if ($host = $_SERVER['HTTP_X_FORWARDED_HOST'])o x = a == 1 ? True : False. La prima volta che l'ho visto, il mio cervello stava cercando l'istanza di $ host e una risposta per "perché è solo uno" = "segno?". Sto iniziando a non gradire i linguaggi di programmazione di digitazione debole. Tutto è scritto in modo diverso. Non risparmi tempo e non sei speciale. Non scrivo codice in questo modo, perché dopo che il tempo passa, sono io quello che ha bisogno di eseguirne il debug. Sembra davvero disordinato per un cervello stanco! So che il mio inglese è engrish, ma almeno ci provo.
StefanNch,

1
ragazzi, ho semplicemente portato il codice da Symfony. Questo è il modo in cui l'ho preso. Nonostante tutto, funziona e sembra abbastanza approfondito. Anch'io, cosa che non è abbastanza leggibile, ma non ho avuto il tempo di riscriverlo completamente.
antitossico,

2
Mi sta bene. Quelli sono operatori ternari e possono effettivamente risparmiare tempo (e byte) senza diminuire la leggibilità, se usati in modo appropriato.
showdev,

1
@antitoxic, -1 I programmatori di Symfony (come molti altri) non sanno esattamente cosa stanno facendo in questo caso. Questo non ti dà il nome host (vedi la risposta di Simon). Questo solo vi darà una migliore congettura , che sarà sbagliato molte volte.
Pacerier

11

È "sicuro" da utilizzare $_SERVER['HTTP_HOST']per tutti i collegamenti in un sito senza doversi preoccupare degli attacchi XSS, anche se utilizzati nei moduli?

Sì, è sicuro da usare $_SERVER['HTTP_HOST'](e persino $_GETe $_POST) purché tu li verifichi prima di accettarli. Questo è ciò che faccio per i server di produzione sicuri:

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

Il vantaggio di $_SERVER['HTTP_HOST']è che il suo comportamento è più ben definito di $_SERVER['SERVER_NAME']. Contrasto ➫➫ :

Contenuti dell'host: intestazione della richiesta corrente, se presente.

con:

Il nome dell'host del server con cui viene eseguito lo script corrente.

L'uso di un'interfaccia meglio definita come $_SERVER['HTTP_HOST']significa che un numero maggiore di SAPI la implementerà utilizzando un comportamento ben definito e affidabile . (A differenza dell'altro .) Tuttavia, è ancora totalmente dipendente dalla SAPI ➫➫ :

Non vi è alcuna garanzia che ogni server Web fornirà una di queste [ $_SERVERvoci]; i server possono ometterne alcuni o fornire altri non elencati qui.

Per capire come recuperare correttamente il nome host, prima di tutto devi capire che un server che contiene solo codice non ha alcun mezzo per conoscere (prerequisito per la verifica) il proprio nome sulla rete. Deve interfacciarsi con un componente che gli fornisce il proprio nome. Questo può essere fatto tramite:

  • file di configurazione locale

  • database locale

  • codice sorgente hardcoded

  • richiesta esterna ( arricciatura )

  • client / dell'attaccante Host:richiesta

  • eccetera

Di solito viene eseguito tramite il file di configurazione locale (SAPI). Nota che l'hai configurato correttamente, ad es. In Apache ➫➫ :

È necessario "falsificare" un paio di cose per rendere l'host virtuale dinamico simile a un normale.

Il più importante è il nome del server utilizzato da Apache per generare URL autoreferenziali, ecc. È configurato con la ServerNamedirettiva ed è disponibile per i CGI tramite la SERVER_NAMEvariabile di ambiente.

Il valore effettivo utilizzato in fase di esecuzione è controllato dall'impostazione UseCanonicalName.

Con UseCanonicalName Off il nome del server deriva dal contenuto Host:dell'intestazione nella richiesta. Con UseCanonicalName DNS esso proviene da una ricerca DNS inversa dell'indirizzo IP dell'host virtuale. La prima impostazione viene utilizzata per l'hosting virtuale dinamico basato sul nome e la seconda per l'hosting basato su IP **.

Se Apache non può lavorare fuori il nome del server, perché non v'è alcuna Host:intestazione o il DNS lookup fallisce poi il valore configurato con ServerNameviene usato al posto.


8

La differenza principale tra i due è che $_SERVER['SERVER_NAME']è una variabile controllata dal server, mentre $_SERVER['HTTP_HOST']è un valore controllato dall'utente.

La regola empirica è non fidarsi mai dei valori dell'utente, quindi $_SERVER['SERVER_NAME']è la scelta migliore.

Come ha sottolineato Gumbo, Apache costruirà SERVER_NAME a partire da valori forniti dall'utente se non impostato UseCanonicalName On.

Modifica: Detto questo, se il sito utilizza un host virtuale basato sul nome, l'intestazione Host HTTP è l'unico modo per raggiungere siti che non sono il sito predefinito.


Inteso. Il mio hangup è "come può un utente modificare il valore di $ _SERVER ['HTTP_HOST']?" È anche possibile?
Jeff,

5
Un utente può modificarlo perché è solo il contenuto dell'intestazione Host dalla richiesta in arrivo. Il server principale (o VirtualHost associato al valore predefinito : 80) risponderà a tutti gli host sconosciuti, quindi il contenuto del tag Host su quel sito potrebbe essere impostato su qualsiasi cosa.
Powerlord,

4
Nota che gli host virtuali basati su IP risponderanno SEMPRE sul loro IP specifico, quindi non puoi in nessun caso fidarti del valore dell'host HTTP su di essi.
Powerlord,

1
@Jeff, è come chiedere "È possibile chiamare il numero di telefono di Pizza Hut e richiedere di parlare con il personale KFC?" Certo che puoi richiedere tutto quello che vuoi. @Powerlord, questo non ha nulla a che fare con gli host virtuali basati su IP. Il vostro server, indipendentemente dall'host virtuale basato su IP o meno, non può in alcun caso fidarsi del Host:valore HTTP a meno che non lo abbiate già verificato , manualmente o tramite la configurazione di SAPI.
Pacerier

3

Non ne sono sicuro e non mi fido davvero $_SERVER['HTTP_HOST']perché dipende dall'intestazione del client. In un altro modo, se un dominio richiesto dal client non è il mio, non entreranno nel mio sito perché il protocollo DNS e TCP / IP lo indirizzano alla destinazione corretta. Tuttavia, non so se possibile dirottare il server DNS, di rete o persino Apache. Per sicurezza, definisco il nome host nell'ambiente e lo confronto con $_SERVER['HTTP_HOST'].

Aggiungi il SetEnv MyHost domain.comfile .htaccess su root e aggiungi il codice in Common.php

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

Includo questo file Common.php in ogni pagina php. Questa pagina fa tutto il necessario per ogni richiesta come session_start(), modifica i cookie di sessione e rifiuta se il metodo post proviene da domini diversi.


1
Ovviamente è possibile bypassare il DNS. Un utente malintenzionato può semplicemente emettere un Host:valore fradulente direttamente sull'IP del server.
Pacerier

1

XSSsarà sempre lì anche se si utilizza $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']OR$_SERVER['PHP_SELF']


1

Innanzitutto voglio ringraziarti per tutte le risposte e le spiegazioni valide. Questo è il metodo che ho creato in base a tutte le tue risposte per ottenere l'URL di base. Lo uso solo in situazioni molto rare. Quindi NON c'è una grande attenzione ai problemi di sicurezza, come gli attacchi XSS. Forse qualcuno ne ha bisogno.

// 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]; }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.