PHP $ _SERVER ['HTTP_HOST'] เทียบกับ $ _SERVER ['SERVER_NAME'] ฉันเข้าใจหน้าคนถูกต้องหรือไม่?


167

ฉันค้นหามากและอ่านเอกสาร PHP $ _SERVERด้วย ฉันมีสิทธิ์ที่จะใช้สำหรับสคริปต์ PHP ของฉันสำหรับคำจำกัดความลิงก์แบบง่ายที่ใช้ทั่วทั้งไซต์ของฉันหรือไม่?

$_SERVER['SERVER_NAME'] ขึ้นอยู่กับไฟล์ปรับแต่งของเว็บเซิร์ฟเวอร์ของคุณ (Apache2 ในกรณีของฉัน) และแตกต่างกันไปตามคำสั่งไม่กี่: (1) VirtualHost, (2) ServerName, (3) UseCanonicalName ฯลฯ

$_SERVER['HTTP_HOST'] ขึ้นอยู่กับการร้องขอจากลูกค้า

$_SERVER['HTTP_HOST']ดังนั้นก็จะดูเหมือนกับผมว่าคนที่เหมาะสมที่จะใช้ในการที่จะทำให้ฉันเป็นสคริปต์ที่เข้ากันได้เป็นไปได้ที่จะเป็น สมมติฐานนี้ถูกต้องหรือไม่

ความคิดเห็นติดตามผล:

ฉันเดาว่าฉันได้รับความหวาดระแวงเล็กน้อยหลังจากอ่านบทความนี้และสังเกตว่าคนบางคนพูดว่า "พวกเขาจะไม่เชื่อถือ$_SERVERvars ใด ๆ":

เห็นได้ชัดว่าการอภิปรายส่วนใหญ่เกี่ยวกับ$_SERVER['PHP_SELF']และทำไมคุณไม่ควรใช้ในแอตทริบิวต์การกระทำแบบฟอร์มโดยไม่ต้องหลบหนีที่เหมาะสมเพื่อป้องกันการโจมตี XSS

ข้อสรุปของฉันเกี่ยวกับคำถามเดิมของฉันคือการ "ปลอดภัย" เพื่อใช้$_SERVER['HTTP_HOST']สำหรับลิงก์ทั้งหมดในเว็บไซต์โดยไม่ต้องกังวลเกี่ยวกับการโจมตี XSS แม้เมื่อใช้ในรูปแบบ

โปรดแก้ไขฉันหากฉันผิด

คำตอบ:


149

นั่นอาจเป็นความคิดแรกของทุกคน แต่มันก็ยากขึ้นนิดหน่อย ดูบทความคริส Shiflett ของSERVER_NAMEHTTP_HOSTกับ

ดูเหมือนว่าไม่มีกระสุนเงิน เมื่อคุณบังคับให้ Apache ใช้ชื่อบัญญัติเท่านั้นคุณจะได้รับชื่อเซิร์ฟเวอร์ที่ถูกต้องSERVER_NAMEเสมอ

ดังนั้นคุณจะไปกับที่หรือคุณตรวจสอบชื่อโฮสต์กับรายการสีขาว:

$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
ฮ่า ๆ ฉันอ่านบทความนั้น แต่ดูเหมือนจะไม่ตอบคำถามของฉัน นักพัฒนาซอฟต์แวร์คนใดที่ใช้ ถ้าอย่างใดอย่างหนึ่ง
เจฟฟ์

2
Iiiiinteresting ฉันไม่เคยรู้เลยว่า SERVER_NAME ใช้ค่าที่ผู้ใช้ระบุโดยค่าเริ่มต้นใน Apache
Powerlord

1
@Jeff สำหรับเซิร์ฟเวอร์ที่โฮสต์มากกว่าหนึ่งโดเมน / ย่อยคุณมีเพียงสองตัวเลือก$_SERVER['SERVER_NAME']และ$_SERVER['HTTP_HOST'](นอกเหนือจากการใช้ handshake ที่กำหนดเองอื่น ๆ ตามคำขอของผู้ใช้) ผู้พัฒนามืออาชีพไม่เชื่อในสิ่งที่พวกเขาไม่เข้าใจอย่างสมบูรณ์ ดังนั้นพวกเขาจึงมีการตั้งค่า SAPI อย่างถูกต้องอย่างสมบูรณ์ (ในกรณีที่ตัวเลือกที่พวกเขาใช้จะให้ผลลัพธ์ที่ถูกต้อง) หรือพวกเขาจะทำรายการที่อนุญาตพิเศษซึ่งไม่สำคัญว่าค่าซัพพลายของ SAPI จะเป็นเท่าใด
Pacerier

@Gumbo คุณต้องใช้แพตช์ "พอร์ต"เนื่องจากปัญหาร้ายแรงกับ SAPIs บางอย่าง นอกจากนี้ยังarray_key_existsเป็นที่ปรับขนาดได้มากขึ้นเมื่อเทียบกับin_arrayที่มี O (n) ผลการดำเนินงาน
Pacerier

2
@Pacerier array_key_exists และ in_array ทำสิ่งที่แตกต่างกันตรวจสอบคีย์ก่อนหน้าค่าหลังดังนั้นคุณไม่สามารถแลกเปลี่ยนได้ นอกจากนี้ถ้าคุณมีอาร์เรย์ของสองค่าที่คุณไม่ควรจริงๆจะกังวลเกี่ยวกับ O (n) ผลการดำเนินงาน ...
EIS

74

เพียงบันทึกเพิ่มเติม - หากเซิร์ฟเวอร์ทำงานบนพอร์ตอื่นที่ไม่ใช่ 80 (ซึ่งอาจเป็นเรื่องปกติในเครื่องพัฒนา / อินทราเน็ต) จากนั้นจะHTTP_HOSTมีพอร์ตในขณะที่SERVER_NAMEไม่มี

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

(อย่างน้อยนั่นคือสิ่งที่ฉันสังเกตเห็นใน virtualhosts ที่ใช้พอร์ต Apache)

ขณะที่ไมค์ได้ตั้งข้อสังเกตด้านล่างHTTP_HOSTไม่ได้มี:443เมื่อทำงานบน HTTPS (ยกเว้นกรณีที่คุณกำลังทำงานอยู่บนพอร์ตที่ไม่ได้มาตรฐานซึ่งฉันไม่ได้ทดสอบ)


4
หมายเหตุ: พอร์ตนี้ไม่มีอยู่ใน HTTP_HOST สำหรับ 443 (พอร์ต SSL ดีฟอลต์)
Mike

ดังนั้นในคำอื่น ๆ ค่าของHTTP_HOSTไม่ตรงHost:พารามิเตอร์ที่ผู้ใช้ให้ มันขึ้นอยู่กับว่า
Pacerier

1
@Pacerier ไม่ตรงข้าม: HTTP_HOST เป็นฟิลด์ Host: ที่มาพร้อมกับคำขอ HTTP พอร์ตเป็นส่วนหนึ่งของมันและเบราว์เซอร์ไม่ได้พูดถึงเมื่อมันเป็นค่าเริ่มต้น (80 สำหรับ HTTP; 443 สำหรับ HTTPS)
xhienne

29

ใช้อย่างใดอย่างหนึ่ง ทั้งสองมีความปลอดภัยเท่ากัน (ใน) ปลอดภัยเนื่องจากในหลายกรณี SERVER_NAME เพิ่งมีการเติมข้อมูลจาก HTTP_HOST ปกติแล้วฉันจะใช้ HTTP_HOST เพื่อให้ผู้ใช้อยู่ในชื่อโฮสต์ที่แน่นอนที่พวกเขาเริ่มต้น ตัวอย่างเช่นหากฉันมีเว็บไซต์เดียวกันบนโดเมน. com และ. org ฉันไม่ต้องการส่งคนจาก. org ถึง. com โดยเฉพาะหากพวกเขาอาจมีโทเค็นการเข้าสู่ระบบใน. org ที่พวกเขาสูญเสียหากส่งไป โดเมนอื่น

ไม่ว่าจะด้วยวิธีใดคุณต้องมั่นใจว่าเว็บแอปของคุณจะตอบสนองต่อโดเมนที่รู้จักดีเท่านั้น สิ่งนี้สามารถทำได้ (a) ด้วยการตรวจสอบด้านแอปพลิเคชันเช่น Gumbo's หรือ (b) โดยใช้โฮสต์เสมือนในชื่อโดเมนที่คุณต้องการที่ไม่ตอบสนองต่อคำขอที่ให้ส่วนหัวโฮสต์ที่ไม่รู้จัก

เหตุผลก็คือถ้าคุณอนุญาตให้ไซต์ของคุณเข้าถึงได้ภายใต้ชื่อเก่า ๆ คุณจะเปิดการโจมตี DNS rebinding ด้วยตัวเอง (ที่ชื่อโฮสต์ของไซต์อื่นชี้ไปที่ IP ของคุณผู้ใช้เข้าถึงไซต์ของคุณด้วยชื่อโฮสต์ของผู้โจมตี ถูกย้ายไปที่ IP ของผู้โจมตีนำคุกกี้ / รับรองความถูกต้องไปด้วย) และการไฮแจ็กเครื่องมือค้นหา (ที่ผู้โจมตีชี้ชื่อโฮสต์ของตนเองที่เว็บไซต์ของคุณและพยายามทำให้เครื่องมือค้นหามองว่าเป็นชื่อโฮสต์ที่ดีที่สุด)

เห็นได้ชัดว่าการสนทนาส่วนใหญ่เกี่ยวกับ $ _SERVER ['PHP_SELF'] และสาเหตุที่คุณไม่ควรใช้ในแอ็ตทริบิวต์การกระทำแบบฟอร์มโดยไม่มีการหลบหนีที่เหมาะสมเพื่อป้องกันการโจมตี XSS

pfft ดีที่คุณไม่ควรใช้อะไรในการใด ๆแอตทริบิวต์โดยไม่ต้องหลบหนีด้วยhtmlspecialchars($string, ENT_QUOTES)เพื่อให้มีอะไรพิเศษเกี่ยวกับตัวแปรที่มีเซิร์ฟเวอร์


อยู่กับโซลูชัน (a), (b) ไม่ปลอดภัยจริง ๆ โดยใช้ URI แบบสัมบูรณ์ในคำขอ HTTP อนุญาตให้ใช้การเลี่ยงผ่านความปลอดภัยของชื่อโฮสต์เสมือน ดังนั้นกฎที่แท้จริงไม่เคยเชื่อถือ SERVER_NAME หรือ HTTP_HOST
regilero

@bince, เครื่องมือค้นหาที่กล่าวถึงนี้ถูกลักลอบใช้งานอย่างไร เครื่องมือค้นหาจับคู่คำกับURL ของโดเมนโดยไม่เกี่ยวข้องกับ IP เหตุใดคุณจึงกล่าวว่า "ผู้โจมตีสามารถทำให้เครื่องมือค้นหาดูattacker.comว่าเป็นแหล่งข้อมูลหลักที่ดีที่สุดสำหรับ IP ของเซิร์ฟเวอร์ของคุณ" หมายความว่าอย่างไร ดูเหมือนจะไม่ได้มีความหมายอะไรกับเครื่องมือค้นหาอะไรจะเกิดขึ้น?
Pacerier

2
Google แน่นอนมี (และอาจจะยังคงมีในรูปแบบบาง) แนวคิดของเว็บไซต์ล่อเพื่อที่ว่าถ้าเว็บไซต์ของคุณสามารถเข้าถึงได้เป็นhttp://example.com/, http://www.example.com/และhttp://93.184.216.34/มันจะรวมไว้ในเว็บไซต์หนึ่งเลือกที่นิยมมากที่สุดของที่อยู่และมีเพียงเชื่อมโยงกลับไปที่ รุ่น หากคุณสามารถชี้evil-example.comไปยังที่อยู่เดียวกันและทำให้ Google เห็นว่าเป็นที่อยู่ที่ได้รับความนิยมมากกว่าคุณสามารถขโมยน้ำผลไม้ของเว็บไซต์ได้ ฉันไม่รู้ว่านี่มันใช้งานได้จริงวันนี้ แต่ฉันเคยเห็นผู้โจมตีฟาร์มลิงค์ของรัสเซียพยายามทำมันในอดีต
bobince

24

นี่คือการแปลแบบละเอียดของสิ่งที่ Symfony ใช้เพื่อรับชื่อโฮสต์ ( ดูตัวอย่างที่สองสำหรับการแปลตามตัวอักษรมากขึ้น ):

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

เก่า:

นี่คือการแปลของฉันเกี่ยวกับ PHP ของวิธีที่ใช้ในเฟรมเวิร์ก Symfony ที่พยายามรับชื่อโฮสต์จากทุกวิธีที่เป็นไปได้เพื่อการปฏิบัติที่ดีที่สุด:

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 โปรดระบุ "วิธีนี้"
showdev

1
@showdev ผมพบว่า "ยาก" ที่จะอ่านคำสั่งสภาพเหมือนหรือif ($host = $_SERVER['HTTP_X_FORWARDED_HOST']) x = a == 1 ? True : Falseครั้งแรกที่ฉันเห็นว่าสมองของฉันกำลังมองหาการสร้างอินสแตนซ์ของ $ host และคำตอบสำหรับ "ทำไมมีเพียงหนึ่งสัญญาณ" = "? ฉันเริ่มไม่ชอบภาษาการเขียนโปรแกรมที่อ่อนแอ ทุกอย่างเขียนแตกต่างกัน คุณไม่ต้องเสียเวลาและคุณไม่ได้พิเศษ ฉันไม่ได้เขียนโค้ดด้วยวิธีนี้เพราะหลังจากผ่านไปแล้วฉันเป็นคนที่ต้องการแก้ไขข้อบกพร่อง ดูเหมือนจะยุ่งจริงๆสำหรับสมองที่เหนื่อยล้า! ฉันรู้ว่าภาษาอังกฤษของฉันเป็นสิ่งที่น่ารังเกียจ แต่อย่างน้อยฉันก็ลอง
StefanNch

1
พวกฉันเพียงแค่ส่งรหัสจาก Symfony นี่คือวิธีที่ฉันใช้ ทุกอย่างสำคัญ - ใช้งานได้และดูเหมือนค่อนข้างละเอียด ฉันตัวฉันเองยังมีสิ่งนี้ไม่สามารถอ่านได้เพียงพอ แต่ฉันไม่มีเวลาเขียนใหม่ทั้งหมด
ต้านพิษ

2
ดูดีสำหรับฉัน สิ่งเหล่านี้คือตัวดำเนินการที่ประกอบไปด้วยกันและจริง ๆ แล้วสามารถประหยัดเวลา (และไบต์) โดยไม่ทำให้การอ่านลดลงเมื่อใช้อย่างเหมาะสม
showdev

1
@antitoxic, -1 Symfony coders (เหมือนคนอื่น ๆ ) ไม่ทราบว่าพวกเขากำลังทำอะไรในกรณีนี้ นี่ไม่ได้ให้ชื่อโฮสต์แก่คุณ (ดูคำตอบของ Simon) นี่เป็นเพียงการเดาที่ดีที่สุดซึ่งจะผิดหลายครั้ง
Pacerier

11

ปลอดภัยหรือไม่ที่จะใช้$_SERVER['HTTP_HOST']สำหรับลิงก์ทั้งหมดในเว็บไซต์โดยไม่ต้องกังวลเกี่ยวกับการโจมตี XSS แม้ว่าจะใช้ในรูปแบบหรือไม่

ใช่มันปลอดภัยที่จะใช้$_SERVER['HTTP_HOST'](และแม้แต่$_GETและ$_POST) ตราบใดที่คุณตรวจสอบพวกเขาก่อนที่จะยอมรับพวกเขา นี่คือสิ่งที่ฉันทำกับเซิร์ฟเวอร์ที่ปลอดภัย:

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

ประโยชน์ของการเป็นว่าพฤติกรรมของมันจะดีขึ้นกว่าที่กำหนดไว้$_SERVER['HTTP_HOST'] $_SERVER['SERVER_NAME']ความคมชัด➫➫ :

เนื้อหาของโฮสต์: ส่วนหัวจากคำขอปัจจุบันหากมี

ด้วย:

ชื่อของโฮสต์เซิร์ฟเวอร์ที่สคริปต์ปัจจุบันทำงานอยู่

การใช้อินเทอร์เฟซที่กำหนดไว้ดีกว่า $_SERVER['HTTP_HOST']หมายถึงว่า SAPIs จะใช้งานได้มากขึ้นโดยใช้พฤติกรรมที่กำหนดไว้อย่างน่าเชื่อถือ (ไม่เหมือนที่อื่น ) อย่างไรก็ตามมันยังคงพึ่งพา SAPI ทั้งหมดtotally :

ไม่รับประกันว่าทุกเว็บเซิร์ฟเวอร์จะให้ [ $_SERVERรายการ] เหล่านี้ใด ๆ; เซิร์ฟเวอร์อาจละเว้นบางเซิร์ฟเวอร์หรือให้บริการอื่นที่ไม่อยู่ในรายการนี้

เพื่อให้เข้าใจถึงวิธีการเรียกชื่อโฮสต์ได้อย่างถูกต้องอันดับแรกและสำคัญที่สุดคุณต้องเข้าใจว่าเซิร์ฟเวอร์ที่มีรหัสเท่านั้นไม่มีวิธีที่จะทราบ (จำเป็นต้องมีการตรวจสอบล่วงหน้า) ชื่อของตัวเองบนเครือข่าย มันต้องการที่จะเชื่อมต่อกับองค์ประกอบที่เป็นชื่อของมันเอง สามารถทำได้ผ่าน:

  • ไฟล์กำหนดค่าท้องถิ่น

  • ฐานข้อมูลท้องถิ่น

  • รหัสที่มา hardcoded

  • คำขอภายนอก ( curl )

  • Host:คำขอของลูกค้า / ผู้โจมตี

  • ฯลฯ

โดยปกติจะทำผ่านไฟล์ปรับแต่ง local (SAPI) โปรดทราบว่าคุณได้กำหนดค่าไว้อย่างถูกต้องเช่นใน Apache ➫➫ :

สองสิ่งต่าง ๆ จะต้อง 'ปลอม' เพื่อทำให้โฮสต์เสมือนแบบไดนามิกดูเหมือนปกติ

ที่สำคัญที่สุดคือชื่อเซิร์ฟเวอร์ที่ Apache ใช้เพื่อสร้าง URL อ้างอิงตนเองเป็นต้นซึ่งได้รับการกำหนดค่าด้วยServerNameคำสั่งและสามารถใช้งานได้กับ CGIs ผ่านSERVER_NAMEตัวแปรสภาพแวดล้อม

ค่าจริงที่ใช้ในขณะดำเนินการถูกควบคุมโดยการตั้งค่า UseCanonicalName

ด้วย UseCanonicalName Offชื่อเซิร์ฟเวอร์มาจากเนื้อหาของHost:ส่วนหัวในคำขอ ด้วย UseCanonicalName DNSมันมาจากการค้นหา DNS ย้อนกลับของที่อยู่ IP ของโฮสต์เสมือน การตั้งค่าแบบเดิมใช้สำหรับการโฮสต์เสมือนแบบไดนามิกที่ใช้ชื่อและการตั้งค่าแบบหลังใช้สำหรับการโฮสต์แบบ IP **

หาก Apache ไม่สามารถทำงานได้ออกชื่อเซิร์ฟเวอร์เพราะไม่มีHost:หัวหรือ DNS การค้นหาล้มเหลวแล้วค่าการกำหนดค่าด้วยServerNameใช้แทน


8

ความแตกต่างที่สำคัญระหว่างสองคือนั่น$_SERVER['SERVER_NAME']คือตัวแปรควบคุมเซิร์ฟเวอร์ในขณะที่$_SERVER['HTTP_HOST']เป็นค่าที่ผู้ใช้ควบคุม

กฎง่ายๆคือการไม่ไว้วางใจค่าจากผู้ใช้จึง$_SERVER['SERVER_NAME']เป็นทางเลือกที่ดีกว่า

ในฐานะที่เป็นกระเจี๊ยบชี้ Apache จะสร้าง SERVER_NAME UseCanonicalName Onจากค่าผู้ใช้จัดถ้าคุณไม่ได้ตั้งค่า

แก้ไข: ต้องบอกทุกอย่างว่าถ้าเว็บไซต์ใช้โฮสต์เสมือนตามชื่อส่วนหัวโฮสต์ HTTP เป็นวิธีเดียวในการเข้าถึงไซต์ที่ไม่ใช่ไซต์เริ่มต้น


เข้าใจ Hangup ของฉันคือ "ผู้ใช้จะเปลี่ยนค่าของ $ _SERVER ['HTTP_HOST'] ได้อย่างไร" เป็นไปได้ไหม
Jeff

5
ผู้ใช้สามารถแก้ไขได้เพราะเป็นเพียงเนื้อหาของส่วนหัวโฮสต์จากคำขอที่เข้ามา เซิร์ฟเวอร์หลัก (หรือ VirtualHost ผูกไว้กับค่าเริ่มต้น : 80) จะตอบสนองต่อโฮสต์ที่ไม่รู้จักทั้งหมดดังนั้นเนื้อหาของแท็กโฮสต์บนไซต์นั้นสามารถตั้งค่าเป็นอะไรก็ได้
Powerlord

4
โปรดทราบว่า IP-based เสมือนครอบครัวมักจะตอบสนองบน IP เฉพาะของพวกเขาดังนั้นคุณจึงไม่สามารถภายใต้สถานการณ์ใดไว้วางใจค่า HTTP โฮสต์ที่พวกเขา
Powerlord

1
@ เจฟฟ์มันเหมือนถามว่า "เป็นไปได้ไหมที่จะโทรไปที่หมายเลขโทรศัพท์ของพิซซ่าฮัทและขอพูดกับพนักงานเคเอฟซี" แน่นอนว่าคุณสามารถขออะไรก็ได้ที่คุณต้องการ @ Powerlord สิ่งนี้ไม่เกี่ยวข้องกับโฮสต์เสมือนบน IP เซิร์ฟเวอร์ของคุณไม่ว่าจะใช้โฮสต์เสมือนที่ใช้ IP หรือไม่ก็ตามไม่สามารถเชื่อถือค่า HTTP ได้Host:เว้นแต่ว่าคุณได้ทำการตรวจสอบแล้วด้วยตนเองหรือผ่านการตั้งค่า SAPI ของคุณ
Pacerier

3

ฉันไม่แน่ใจและไม่ไว้วางใจจริงๆ$_SERVER['HTTP_HOST']เพราะมันขึ้นอยู่กับส่วนหัวของลูกค้า อีกวิธีหนึ่งหากโดเมนที่ลูกค้าร้องขอไม่ใช่ของฉันพวกเขาจะไม่เข้าสู่เว็บไซต์ของฉันเพราะโปรโตคอล DNS และ TCP / IP ชี้ไปยังปลายทางที่ถูกต้อง อย่างไรก็ตามฉันไม่รู้ว่าเป็นไปได้ที่จะจี้ DNS เครือข่ายหรือแม้กระทั่งเซิร์ฟเวอร์ Apache เพื่อความปลอดภัยฉันจะกำหนดชื่อโฮสต์ในสภาพแวดล้อมและเปรียบเทียบกับ$_SERVER['HTTP_HOST']เพื่อความปลอดภัยฉันกำหนดชื่อโฮสต์ในสภาพแวดล้อมและเปรียบเทียบกับ

เพิ่มSetEnv MyHost domain.comในไฟล์. htaccess บนรูทและเพิ่มรหัส ths ใน Common.php

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

ฉันรวมไฟล์ Common.php นี้ไว้ในหน้า php ทุกหน้า หน้านี้ทำสิ่งที่จำเป็นสำหรับแต่ละคำขอเช่นsession_start()แก้ไขคุกกี้เซสชันและปฏิเสธหากวิธีการโพสต์มาจากโดเมนที่ต่างกัน


1
แน่นอนว่าเป็นไปได้ที่จะเลี่ยง DNS ผู้โจมตีสามารถออกHost:ค่าfradulent โดยตรงไปยัง IP ของเซิร์ฟเวอร์ของคุณ
Pacerier


1

ก่อนอื่นฉันอยากจะขอบคุณสำหรับคำตอบและคำอธิบายที่ดีทั้งหมด นี่คือวิธีที่ฉันสร้างขึ้นตามคำตอบทั้งหมดของคุณเพื่อรับ URL พื้นฐาน ฉันใช้มันในสถานการณ์ที่หายากมากเท่านั้น ดังนั้นจึงไม่ได้เน้นเรื่องความปลอดภัยเช่นการโจมตี XSS บางทีบางคนต้องการมัน

// 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]; }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.