PHP $ _SERVER ['HTTP_HOST'] ve $ _SERVER ['SERVER_NAME'] ile man sayfalarını doğru anlıyor muyum?


167

Ben çok arama yaptım ve ayrıca PHP $ _SERVER belgelerini okuyun . Sitem boyunca kullanılan basit bağlantı tanımları için PHP betiklerim için hangisinin kullanılacağı konusunda bu hakkım var mı?

$_SERVER['SERVER_NAME'] web sunucunuzun yapılandırma dosyasına (benim durumumda Apache2) dayanır ve birkaç yönerge bağlı olarak değişir: (1) VirtualHost, (2) SunucuAdı, (3) UseCanonicalName, vb.

$_SERVER['HTTP_HOST'] istemciden gelen isteği temel alır.

Bu nedenle, scriptlerimi olabildiğince uyumlu hale getirmek için kullanılacak doğru olanın bana göre olduğu görülüyor $_SERVER['HTTP_HOST']. Bu varsayım doğru mu?

Takip yorumları:

Bu makaleyi okuduktan ve bazı millet " $_SERVERvars hiçbir güven olmaz" dedi dikkat sonra biraz paranoyak var sanırım :

Görünüşe göre tartışma esas olarak $_SERVER['PHP_SELF']ve neden XSS saldırılarını önlemek için uygun bir şekilde kaçmadan form eylem özelliğinde kullanmamanız gerektiği.

Yukarıdaki orijinal sorumla ilgili sonucum $_SERVER['HTTP_HOST'], formlarda kullanıldığında bile XSS saldırıları hakkında endişelenmenize gerek kalmadan bir sitedeki tüm bağlantılar için kullanılmanın "güvenli" olduğudur.

Yanılıyorsam lütfen beni düzeltin.

Yanıtlar:


149

Muhtemelen bu herkesin ilk düşüncesi. Ama bu biraz daha zor. Bkz Chris Shiflett makalesini SERVER_NAMEKarşıHTTP_HOST .

Görünüşe göre gümüş mermi yok. Yalnızca Apache'yi kurallı adı kullanmaya zorladığınızda , her zaman doğru sunucu adını alırsınız SERVER_NAME.

Böylece bununla devam edersiniz veya ana makine adını beyaz bir listeye göre kontrol edersiniz:

$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, bu makaleyi okudum ve soruma cevap vermiyor gibi görünüyor. Profesyoneller hangisini kullanıyor? Eğer ikisinden biri.
Jeff

2
İlginçtir, SERVER_NAME uygulamasının Apache'de varsayılan olarak kullanıcı tarafından sağlanan değerleri kullandığını bilmiyordum.
Powerlord

1
@Jeff, Birden fazla alt / etki alanı barındıran sunucular için yalnızca iki seçeneğiniz vardır $_SERVER['SERVER_NAME']ve $_SERVER['HTTP_HOST'](kullanıcı isteğine göre başka bir özel el sıkışma uygulamak dışında). Profesyoneller tam olarak anlamadıkları şeylere güvenmiyorlar. Onlar da kendi SAPI var Yani mükemmel doğru kurulum (bu durumda kullandıkları seçenek olacaktır doğru sonucu verir) veya onlar SAPI arzını hangi değerleri önemli değil, öyle ki beyaz liste yapacağız.
Pacerier

@ Gumbo, Bazı SAPI'lerle ilgili ciddi sorunlar nedeniyle "bağlantı noktası" yamasını uygulamanız gerekir . Aynı zamanda, array_key_existsbir daha ölçeklenebilir kıyasla in_arraybu sahiptir O (n) performansı.
Pacerier

2
@Pacerier array_key_exists ve in_array farklı şeyler yapar, eski anahtar kontrolleri, son değerler, böylece bunları değiştiremezsiniz. Ayrıca, iki değerden oluşan bir diziniz varsa, O (n) performansı hakkında gerçekten endişelenmemelisiniz ...
eis

74

Sadece ek bir not - sunucu 80 dışında bir bağlantı noktasında çalışıyorsa (bir geliştirme / intranet makinesinde ortak olabileceği gibi) HTTP_HOSTbağlantı noktasını içerir, ancak SERVER_NAMEiçermez.

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

(En azından Apache port tabanlı sanal ana bilgisayarlarda bunu fark ettim)

Mike aşağıda belirtildiği gibi, HTTP_HOSTyok değil içeriyor :443(eğer ben test etmedim standart olmayan bir port, çalışan sürece) HTTPS çalışırken.


4
Not: Bağlantı noktası HTTP_HOST içinde 443 için de yoktur (varsayılan SSL bağlantı noktası).
Mike

Yani başka bir deyişle, değeri HTTP_HOSTtam olarak Host:kullanıcının sağladığı parametre değildir . Sadece buna dayanıyor.
Pacerier

1
@Pacerier Hayır, tam tersi: HTTP_HOST, HTTP isteği ile sağlanan Host: alanıdır. Bağlantı noktası bir parçasıdır ve tarayıcılar varsayılan
olandan

29

Either "kalıbını kullanınız. Her ikisi de eşit derecede güvenlidir, çünkü çoğu durumda SERVER_NAME zaten HTTP_HOST'tan doldurulmuştur. Ben normalde HTTP_HOST için gidiyorum, böylece kullanıcı başladığı kesin ana bilgisayar adı kalır. Örneğin, bir .com ve .org alan adında aynı siteye sahipsem, .org'dan .com'ya birilerini göndermek istemiyorum, özellikle .org'da giriş token'leri gönderildiyse kaybedecekleri diğer etki alanı.

Her iki durumda da, web uygulamanızın yalnızca bilinen iyi alanlara yanıt vereceğinden emin olmanız gerekir. Bu, (a) Gumbo's gibi bir uygulama tarafı denetimi ile veya (b) bilinmeyen bir Ana Bilgisayar üstbilgisi veren isteklere yanıt vermeyen, istediğiniz etki alanı adlarında sanal bir ana bilgisayar kullanılarak yapılabilir .

Bunun nedeni, sitenize eski bir adla erişilmesine izin verirseniz, kendinizi DNS yeniden bağlama saldırılarına (başka bir sitenin ana bilgisayar adının IP'nizi gösterdiği yerlerde) bir kullanıcının sitenize saldırganın ana bilgisayar adıyla, ardından ana bilgisayar adıyla erişmesidir. çerezleri / yetkilerini alarak) ve arama motoru kaçırma (saldırganın sitenizdeki kendi ana bilgisayar adını işaret ettiği ve arama motorlarının 'en iyi' birincil ana bilgisayar adı olarak görmesini sağlamaya çalıştığı).

Görünüşe göre tartışma esas olarak $ _SERVER ['PHP_SELF'] ve XSS saldırılarını önlemek için neden uygun şekilde kaçmadan form action özelliğinde kullanmamanız gerektiği.

Pfft. Iyi kullanmamalısınız şey içinde herhangi birlikte kaçan olmadan özniteliği htmlspecialchars($string, ENT_QUOTES)yani orada sunucu değişkenleri hakkında özel bir şey yok.


Çözüm (a) ile kal, (b) gerçekten güvenli değildir, HTTP isteklerinde mutlak URI kullanarak ad tabanlı sanal ana bilgisayar güvenlik baypasına izin verir. Yani gerçek kural asla SERVER_NAME veya HTTP_HOST'a güvenmemek.
regilero

@bobince, Bahsedilen arama motoru kaçırma nasıl çalışır? Arama motorları kelimeleri alan URL'leriyle eşleştirir , IP'lerle ilgilenmezler. Öyleyse neden "bir saldırganın arama motorlarının attacker.comsunucunuzun IP'si için en iyi birincil kaynak olarak görmesini sağlayabilir" olduğunu söylüyorsunuz ? Bu arama motorlarına bir şey ifade etmiyor gibi görünüyor, Bu ne yapacak?
Pacerier

2
Google kesinlikle vardı (ve muhtemelen hala bazı formda vardır) Siteniz olarak erişilebilir olup olmadığını böylece, dupe siteleri kavramını http://example.com/, http://www.example.com/ve http://93.184.216.34/bu tek bir sitede bunları birleştirmek istiyorum, adreslerinin en popüler seçin ve sadece o bağlantıları dönmek sürümü. evil-example.comAynı adresi gösterebilir ve Google'a kısaca daha popüler adres olarak sitenin suyunu çalabileceğini görmesini sağlayabilirseniz. Bugün bunun ne kadar pratik olduğunu bilmiyorum ama Rus bağlantı çiftlik saldırganlarının geçmişte yapmaya çalıştıklarını gördüm.
bobince

24

Bu, Symfony'un ana bilgisayar adını almak için kullandığı şeyin ayrıntılı bir çevirisidir ( daha gerçek bir çeviri için ikinci örneğe bakın ):

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

demode:

Bu benim en iyi uygulama sırasına göre ana bilgisayar adını mümkün olan her yoldan almaya çalışan Symfony çerçevesinde kullanılan bir yöntemin çıplak PHP için benim çeviri:

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 Lütfen "bu şekilde" tanımlayın.
showdev

1
@showdev Durum ifadesini okumak için gerçekten "zor" if ($host = $_SERVER['HTTP_X_FORWARDED_HOST'])ya da gibi buluyorum x = a == 1 ? True : False. Bunu ilk gördüğümde beynim $ host örneği ve "neden sadece bir tane" = "işareti?" Zayıf yazma programlama dillerinden hoşlanmıyorum. Her şey farklı yazılmıştır. Zamandan tasarruf etmiyorsunuz ve özel değilsiniz. Bu şekilde kod yazmıyorum, çünkü zaman geçtikten sonra, hata ayıklaması gereken kişi benim. Yorgun bir beyin için gerçekten dağınık görünüyor! İngilizcemin ingilizce olduğunu biliyorum, ama en azından deniyorum.
StefanNch

1
arkadaşlar, kodu Symfony'dan aldım. Bu şekilde aldım. Her şey için önemli - işe yarıyor ve oldukça kapsamlı görünüyor. Ben, kendim, bunun yeterince okunamayacağı şey ama tamamen yeniden yazmak için zamanım olmadı.
antitoksik

2
Bana iyi geliyor. Bunlar üçlü operatörlerdir ve uygun şekilde kullanıldığında okunabilirliği azaltmadan zamandan (ve baytlardan) tasarruf edebilirler.
showdev

1
@antitoksik, -1 Symfony kodlayıcıları (diğerleri gibi) bu durumda ne yaptıklarını tam olarak bilmiyorlar. Bu size ana bilgisayar adını vermez (Simon'un cevabına bakın). Bu sadece size birçok kez yanlış olacak en iyi tahminde bulunur .
Pacerier

11

$_SERVER['HTTP_HOST']Bir sitedeki tüm bağlantılar için, XSS saldırıları hakkında endişelenmenize gerek kalmadan, formlarda kullanıldığında "güvenli" midir?

Evet, kabul etmeden önce doğruladığınız sürece kullanmak güvenlidir$_SERVER['HTTP_HOST'] (ve hatta $_GETve $_POST) . Güvenli üretim sunucuları için yaptığım şey:

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

Avantajı $_SERVER['HTTP_HOST'], davranışının daha iyi tanımlanmış olmasıdır $_SERVER['SERVER_NAME']. Kontrast ➫➫ :

Ana Makinenin İçeriği: varsa, geçerli istekten başlık.

ile:

Geçerli komut dosyasının altında çalıştığı sunucu ana bilgisayarının adı.

Daha iyi tanımlanmış bir arayüz kullanmak, daha $_SERVER['HTTP_HOST']fazla SAPI'nin güvenilir ve iyi tanımlanmış davranış kullanarak onu uygulayacağı anlamına gelir . (Farklı diğer .) Bununla birlikte, yine de bağımlı tamamen SAPI ➫➫ :

Her web sunucusunun bu [ $_SERVERgirişlerden] herhangi birini sağlayacağının garantisi yoktur ; sunucular bazılarını atlayabilir veya burada listelenmeyen başkalarını sağlayabilir.

Ana bilgisayar adının doğru bir şekilde nasıl alınacağını anlamak için, öncelikle ve en önemlisi, yalnızca kod içeren bir sunucunun ağdaki kendi adını bilmenin (doğrulamak için ön koşul) olmadığını anlamanız gerekir . Kendi adını sağlayan bir bileşenle arayüz kurması gerekir. Bu şu şekilde yapılabilir:

  • yerel yapılandırma dosyası

  • yerel veritabanı

  • sabit kodlu kaynak kodu

  • dış istek ( kıvırmak )

  • müşteri / saldırganın Host:isteği

  • vb

Genellikle yerel (SAPI) yapılandırma dosyası üzerinden yapılır. Düzgün yapılandırdığınıza dikkat edin, örneğin Apache ➫➫ :

Dinamik sanal ana makinenin normal gibi görünmesi için birkaç şeyin 'sahte' olması gerekir.

En önemlisi, Apache tarafından kendinden referanslı URL'ler vb. Oluşturmak için kullanılan sunucu adıdır. ServerNameYönerge ile yapılandırılır ve SERVER_NAMEortam değişkeni aracılığıyla CGI'lar tarafından kullanılabilir .

Çalışma zamanında kullanılan gerçek değer UseCanonicalName ayarı tarafından kontrol edilir .

UseCanonicalName OffSunucu adı ileHost: istekte başlık içeriğinden gelir . Bununla UseCanonicalName DNS birlikte, sanal ana bilgisayarın IP adresinin ters DNS aramasından gelir. İlk ayar, ad tabanlı dinamik sanal barındırma için kullanılırken, ikinci ayar ** IP tabanlı barındırma için kullanılır.

Eğer hiçbir olmadığından Apache sunucusunun ismini çalışamaz Host:başlık veya başarısız arama DNS sonra yapılandırılmış değeri ServerNameyerine kullanılır.


8

İkisi arasındaki en büyük fark $_SERVER['SERVER_NAME'], sunucu tarafından kontrol edilen bir değişken iken $_SERVER['HTTP_HOST'], kullanıcı tarafından kontrol edilen bir değerdir.

Temel kural, kullanıcıdan değerlere asla güvenmemektir, bu yüzden $_SERVER['SERVER_NAME']daha iyi bir seçimdir.

Gumbo'nun belirttiği gibi, Apache, ayarlamazsanız SERVER_NAME adlı kullanıcıyı kullanıcı tarafından sağlanan değerlerden oluşturur UseCanonicalName On.

Düzenleme: Tüm bunları söyledikten sonra, site ad tabanlı bir sanal ana bilgisayar kullanıyorsa, HTTP Ana Bilgisayar üstbilgisi varsayılan site olmayan sitelere erişmenin tek yoludur.


Anladım. Hangout'um "bir kullanıcı $ _SERVER ['HTTP_HOST'] değerini nasıl değiştirebilir?" Hatta mümkün mü?
Jeff

5
Bir kullanıcı, gelen istekte yalnızca Host üstbilgisinin içeriği olduğu için bunu değiştirebilir. Ana sunucu (veya varsayılana bağlı VirtualHost : 80) bilinmeyen tüm ana bilgisayarlara yanıt verir, böylece o sitedeki Ana Makine etiketinin içeriği herhangi bir şeye ayarlanabilir.
Powerlord

4
IP tabanlı sanal ana makinelerin HER ZAMAN kendi IP'lerine yanıt vereceğini unutmayın, bu nedenle hiçbir koşulda HTTP Ana Bilgisayar değerine güvenemezsiniz.
Powerlord

1
@Jeff, "Pizza kulübünün telefon numarasını aramak ve KFC personeli ile konuşmak istemek mümkün mü?" Tabii ki istediğiniz her şeyi talep edebilirsiniz . @ Powerlord, IP tabanlı sanal ana bilgisayarlarla hiçbir ilgisi yoktur. IP tabanlı sanal ana makine olsun veya olmasın, sunucunuz, manuel olarak veya SAPI kurulumunuz aracılığıyla Host:daha önce doğrulamadıysanız , HTTP'nin değerine hiçbir şekilde güvenemez .
Pacerier

3

Emin değilim ve gerçekten güvenmiyorum $_SERVER['HTTP_HOST']çünkü istemcinin başlığına bağlı. Başka bir deyişle, istemci tarafından istenen bir etki alanı benimki değilse, DNS ve TCP / IP protokolü doğru hedefe işaret ettiğinden siteme girmezler. Ancak, DNS, ağ ve hatta Apache sunucusunu ele geçirmenin mümkün olup olmadığını bilmiyorum. Güvende olmak için, ortamdaki ana bilgisayar adını tanımlar ve ile karşılaştırırım $_SERVER['HTTP_HOST'].

Ekle SetEnv MyHost domain.comkök .htaccess dosyasında ve common.php'de ths kodu ekleyin

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

Ben her php sayfasına bu Common.php dosyasını dahil. Bu sayfa session_start(), oturum çerezini değiştirme ve yayınlama yöntemi farklı alan adından geliyorsa reddetme gibi her istek için gereken her şeyi yapar .


1
Elbette DNS'yi atlamak mümkündür. Saldırgan, Host:doğrudan sunucunuzun IP'sine fradulent değeri verebilir.
Pacerier

1

XSSkullansanız bile her zaman orada olacak $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']VEYA$_SERVER['PHP_SELF']


1

Öncelikle tüm iyi cevaplar ve açıklamalar için teşekkür etmek istiyorum. Bu, temel URL'yi almak için tüm yanıtınıza dayanarak oluşturduğum yöntemdir. Sadece çok nadir durumlarda kullanıyorum. Bu nedenle, XSS saldırıları gibi güvenlik sorunlarına büyük bir odak DEĞİLDİR. Belki birinin ihtiyacı vardır.

// 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]; }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.