PHP $ _SERVER [ 'HTTP_HOST'] vs. $ _SERVER [ 'SERVER_NAME'], 매뉴얼 페이지를 올바르게 이해하고 있습니까?


167

나는 많은 검색을했으며 PHP $ _SERVER docs 도 읽었습니다 . 사이트 전체에서 사용되는 간단한 링크 정의를 위해 PHP 스크립트에 사용할 권한에 대해이 권한이 있습니까?

$_SERVER['SERVER_NAME'] 웹 서버의 구성 파일 (내 경우에는 Apache2)을 기반으로하며 (1) VirtualHost, (2) ServerName, (3) UseCanonicalName 등 몇 가지 지시문에 따라 다릅니다.

$_SERVER['HTTP_HOST'] 클라이언트의 요청을 기반으로합니다.

따라서 가능한 한 스크립트를 호환 가능하게 만드는 데 사용할 수있는 적절한 스크립트 인 것 같습니다 $_SERVER['HTTP_HOST']. 이 가정이 맞습니까?

후속 의견 :

나는이 기사를 읽은 후에 약간의 편집증을 앓고 있다고 생각하며 일부 사람들은 "그들은 어떤 종류도 믿지 않을 것"이라고 말했다 $_SERVER.

분명히 토론에 대한 내용 $_SERVER['PHP_SELF']이며 XSS 공격을 방지하기 위해 적절한 이스케이프가 없으면 양식 작업 속성에서 사용해서는 안됩니다.

위의 원래 질문에 대한 나의 결론은 $_SERVER['HTTP_HOST']양식에 사용될 때도 XSS 공격에 대해 걱정할 필요없이 사이트의 모든 링크에 사용하는 것이 "안전"하다는 것입니다 .

내가 틀렸다면 정정 해주세요.

답변:


149

아마 모든 사람의 첫 생각 일 것입니다. 그러나 조금 더 어렵습니다. Chris Shiflett의 기사 SERVER_NAME와 비교를HTTP_HOST 참조하십시오 .

총알이없는 것 같습니다. 아파치가 정식 이름을 사용하도록 강요 할 때만 항상 올바른 서버 이름을 사용할 수 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
롤, 나는 그 기사를 읽었고 실제로 내 질문에 대답하지 않은 것 같습니다. 프로 개발자는 어느 것을 사용합니까? 어느 쪽이든
Jeff

2
흥미롭게도, SERVER_NAME이 Apache에서 기본적으로 사용자 제공 값을 사용한다는 것을 결코 알지 못했습니다.
Powerlord

1
@Jeff, 호스트 하나 개 이상의 서브 / 도메인, 당신은 두 가지 선택을 가지고 서버의 경우 $_SERVER['SERVER_NAME']$_SERVER['HTTP_HOST'](옆으로 사용자의 요구에 따라 다른 사용자 정의 핸드 셰이크를 구현에서). 프로 개발자는 완전히 이해하지 못하는 것을 신뢰하지 않습니다. 따라서 SAPI가 완벽하게 올바르게 설정되어 있거나 (이 경우 사용하는 옵션 이 올바른 결과 제공함) SAPI가 제공하는 값이 중요하지 않도록 화이트리스트를 작성합니다.
Pacerier

@Gumbo, 특정 SAPI의 심각한 문제로 인해 "포트"패치적용 해야합니다 . 또한, 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'

(적어도 아파치 포트 기반 가상 호스트에서 눈치 채 셨을 것입니다)

Mike가 아래에 언급했듯이 HTTPS에서 실행할 때 포함 HTTP_HOST하지 않습니다:443 (테스트하지 않은 비표준 포트에서 실행하지 않는 한).


4
참고 : 포트는 443에 대한 HTTP_HOST에 없습니다 (기본 SSL 포트).
Mike

다시 말해,의 값은 사용자가 제공 HTTP_HOSTHost:매개 변수 가 아닙니다 . 그것은 단지 그것을 기반으로합니다.
Pacerier

1
@Pacerier 아니요, 반대입니다. HTTP_HOST는 HTTP 요청과 함께 제공된 Host : 필드입니다. 포트는 그 일부이며 브라우저는 기본 포트 일 때 언급하지 않습니다 (HTTP의 경우 80, HTTPS의 경우 443)
xhienne

29

둘 중 하나를 사용하십시오. 많은 경우 SERVER_NAME이 HTTP_HOST에서 채워져 있기 때문에 둘 다 똑같이 안전합니다. 일반적으로 HTTP_HOST를 사용하므로 사용자가 시작한 정확한 호스트 이름을 유지합니다. 예를 들어 .com 및 .org 도메인에 동일한 사이트가있는 경우 .org에서 .com으로 누군가를 보내고 싶지 않습니다. 특히 .org에 로그인 토큰이 있으면 .org에서 잃어 버릴 수 있습니다. 다른 도메인.

어느 쪽이든, 웹앱이 알려진 도메인에 대해서만 응답해야합니다. 이 작업은 (a) Gumbo와 같은 응용 프로그램 쪽 확인을 사용하거나 (b) 알 수없는 호스트 헤더를 제공하는 요청에 응답하지 않는 도메인 이름의 가상 호스트를 사용하여 수행 할 수 있습니다.

그 이유는 이전 이름으로 사이트에 액세스 할 수있게되면 DNS 리 바인딩 공격 (다른 사이트의 호스트 이름이 IP를 가리키는 경우 사용자가 공격자의 호스트 이름으로 사이트에 액세스 한 다음 호스트 이름)에 노출 될 수 있기 때문입니다. 는 쿠키 / 인증을 받아 공격자의 IP로 이동하고 검색 엔진 하이재킹 (공격자가 사이트에서 자신의 호스트 이름을 가리키고 검색 엔진이이를 '최상의'기본 호스트 이름으로 보도록 시도합니다).

분명히 $ _SERVER [ 'PHP_SELF']에 대한 논의이며 XSS 공격을 방지하기 위해 적절한 이스케이프없이 form action 속성에서 사용하지 않아야하는 이유가 있습니다.

ff 그럼 당신은 사용하지 말아야 아무것도어떤 으로 탈출하지 않고 속성 htmlspecialchars($string, ENT_QUOTES)때문에이 서버 변수에 대한 아무것도 특별있다.


솔루션 (a), (b)를 유지하는 것은 실제로 안전하지 않습니다. HTTP 요청에 절대 URI를 사용하면 이름 기반 가상 호스트 보안 우회가 가능합니다. 따라서 실제 규칙은 절대로 SERVER_NAME 또는 HTTP_HOST를 신뢰 하지 않습니다 .
regilero 2016 년

@bobince, 언급 된 검색 엔진 하이재킹은 어떻게 작동합니까? 검색 엔진은 단어를 도메인 URL에 매핑 하며 IP를 처리하지 않습니다. 그렇다면 왜 "공격자가 검색 엔진을 attacker.com서버의 IP를위한 최고의 기본 소스로 볼 수있게 만들 수있다 "는 말입니까? 검색 엔진에 아무 의미가없는 것 같습니다. 어떻게해야할까요?
Pacerier

2
구글은 분명했다 (아마 여전히 어떤 형태가) 귀하의 사이트로 액세스 할 수있는 경우 그래서, 속는 사이트의 개념을 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);
}

시대에 뒤쳐진:

이것은 Symfony 프레임 워크에서 사용되는 방법을 맨 PHP로 변환하여 최상의 방법으로 가능한 모든 방법으로 호스트 이름을 가져 오려고합니다.

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 코더 (다른 많은 사람들과 마찬가지로)는이 경우 그들이 무엇을하고 있는지 정확히 알지 못합니다. 호스트 이름은 제공하지 않습니다 (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']입니다. 대조 ➫➫ :

현재 요청의 Host : 헤더 내용 (있는 경우)

와:

현재 스크립트가 실행중인 서버 호스트의 이름입니다.

더 잘 정의 된 인터페이스를 사용 $_SERVER['HTTP_HOST']한다는 것은 더 많은 SAPI가 신뢰할 수있는 잘 정의 된 동작을 사용하여이를 구현한다는 것을 의미합니다 . (달리 다른 .) 그러나, 여전히 의존 완전히 SAPI입니다 ➫➫ :

모든 웹 서버가 이러한 $_SERVER항목을 제공한다고 보장 할 수는 없습니다 . 서버는 일부를 생략하거나 여기에 나열되지 않은 다른 서버를 제공 할 수 있습니다.

호스트 이름을 올바르게 검색하는 방법을 이해하려면 먼저 코드 만 포함 된 서버에는 네트워크에서 자신의 이름 을 알 필요가 없습니다 . 자체 이름을 제공하는 구성 요소와 인터페이스해야합니다. 이것은 다음을 통해 수행 할 수 있습니다.

  • 로컬 설정 파일

  • 로컬 데이터베이스

  • 하드 코드 된 소스 코드

  • 외부 요청 ( )

  • 고객 / 공격자의 Host:요청

  • 기타

일반적으로 로컬 (SAPI) 구성 파일을 통해 수행됩니다. 아파치에서 예를 들어, 올바르게 구성했는지 참고 ➫➫ :

동적 가상 호스트를 일반 가상 호스트처럼 보이게하려면 몇 가지 사항을 '가짜'야합니다.

가장 중요한 것은 Apache가 자체 참조 URL 등을 생성하는 데 사용하는 서버 이름입니다. ServerName지시문으로 구성되며 SERVER_NAME환경 변수 를 통해 CGI에서 사용할 수 있습니다 .

런타임에 사용되는 실제 값 은 UseCanonicalName 설정에 의해 제어됩니다 .

UseCanonicalName Off 서버 이름의 내용에서 오는 Host:요청의 헤더입니다. UseCanonicalName DNS 그 가상 호스트의 IP 주소를 역 DNS 검색에서 온다. 전자의 설정은 이름 기반 동적 가상 호스팅에 사용되고 후자의 설정은 ** IP 기반 호스팅에 사용됩니다.

경우 에는이 없기 때문에 아파치는 서버 이름을 작동 할 수 없습니다 Host:머리글 또는 실패 조회하는 DNS 다음 구성 값 ServerName대신 사용됩니다가.


8

이 둘의 주요 차이점은 $_SERVER['SERVER_NAME']서버 제어 변수이고 $_SERVER['HTTP_HOST']사용자 제어 값입니다.

경험의 원칙은 사용자의 값을 절대 신뢰하지 않는 것이므로 $_SERVER['SERVER_NAME']더 나은 선택입니다.

Gumbo가 지적했듯이 Apache는 설정하지 않으면 사용자 제공 값에서 SERVER_NAME을 생성합니다 UseCanonicalName On.

편집 : 사이트에서 이름 기반 가상 호스트를 사용하는 경우 기본 사이트가 아닌 사이트에 액세스하는 유일한 방법은 HTTP 호스트 헤더입니다.


이해했다. 내 끊기는 "사용자가 $ _SERVER [ 'HTTP_HOST']의 값을 어떻게 변경할 수 있습니까?"입니다. 가능합니까?
Jeff

5
사용자는 들어오는 요청의 Host 헤더 내용이므로 변경할 수 있습니다. 주 서버 (또는 VirtualHost : 기본값 : 80에 바인딩 됨 )는 알 수없는 모든 호스트에 응답하므로 해당 사이트의 호스트 태그 내용을 원하는대로 설정할 수 있습니다.
Powerlord

4
IP 기반 가상 호스트는 항상 특정 IP에 응답하므로 어떤 상황에서도 HTTP 호스트 값을 신뢰할 수 없습니다 .
Powerlord

1
@Jeff는, 그것은 물어 같다 "피자 헛의 전화 번호 및 호출 할 수 있습니다 요청을 KFC 직원에게 말을?" 물론 원하는 것을 요청할 수 있습니다. @Powerlord, 이것은 IP 기반 가상 호스트와 관련이 없습니다. IP 기반 가상 호스트에 관계없이 서버 는 수동으로 또는 SAPI 설정을 통해 이미 확인Host: 하지 않는 한 어떤 상황에서도 HTTP 값을 신뢰할 수 없습니다 .
Pacerier

3

$_SERVER['HTTP_HOST']클라이언트의 헤더에 의존하기 때문에 확실하지 않으며 실제로 신뢰하지 않습니다 . 다른 방법으로, 클라이언트가 요청한 도메인이 내 도메인이 아닌 경우 DNS 및 TCP / IP 프로토콜이 올바른 대상을 가리 키기 때문에 내 사이트로 들어 가지 않습니다. 그러나 가능한 경우 DNS, 네트워크 또는 Apache 서버를 가로 챌 수 있습니다. 안전을 위해 환경에서 호스트 이름을 정의하고와 비교합니다 $_SERVER['HTTP_HOST'].

추가 SetEnv MyHost domain.com루트에 htaccess로 파일과 Common.php에 THS 코드를 추가

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

모든 PHP 페이지에이 Common.php 파일을 포함시킵니다. 이 페이지는 session_start(), 세션 쿠키 수정 및 post 메소드가 다른 도메인에서 온 경우 거부와 같은 각 요청에 필요한 모든 작업을 수행 합니다.


1
물론 DNS를 우회 할 수 있습니다. 공격자는 단순히 Host:서버의 IP에 취약한 값을 직접 발행 할 수 있습니다 .
Pacerier

1

XSS항상 당신이 사용하는 경우에도이있을 것 $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']또는$_SERVER['PHP_SELF']


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.