[php] PHP에서 사용자의 올바른 IP 주소를 검색하는 가장 정확한 방법은 무엇입니까?

IP 주소 검색에 사용할 수 있는 많은 $ _SERVER 변수 헤더 가 있다는 것을 알고 있습니다 . 위의 변수를 사용하여 사용자의 실제 IP 주소를 가장 정확하게 검색하는 방법에 대한 일반적인 합의가 있는지 궁금합니다.

나는 심도있는 솔루션을 찾으려고 노력하고 많은 소스를 기반으로 다음 코드를 생각해 냈습니다. 누군가가 대답에 구멍을 뚫어 내거나 아마도 더 정확한 것에 빛을 비출 수 있다면 그것을 좋아할 것입니다.

@Alix의 최적화가 포함 된 편집

 /**
  * Retrieves the best guess of the client's actual IP address.
  * Takes into account numerous HTTP proxy headers due to variations
  * in how different ISPs handle IP addresses in headers between hops.
  */
 public function get_ip_address() {
  // Check for shared internet/ISP IP
  if (!empty($_SERVER['HTTP_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_CLIENT_IP']))
   return $_SERVER['HTTP_CLIENT_IP'];

  // Check for IPs passing through proxies
  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
   // Check if multiple IP addresses exist in var
    $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
    foreach ($iplist as $ip) {
     if ($this->validate_ip($ip))
      return $ip;
    }
   }
  }
  if (!empty($_SERVER['HTTP_X_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_X_FORWARDED']))
   return $_SERVER['HTTP_X_FORWARDED'];
  if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
   return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
  if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && $this->validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
   return $_SERVER['HTTP_FORWARDED_FOR'];
  if (!empty($_SERVER['HTTP_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_FORWARDED']))
   return $_SERVER['HTTP_FORWARDED'];

  // Return unreliable IP address since all else failed
  return $_SERVER['REMOTE_ADDR'];
 }

 /**
  * Ensures an IP address is both a valid IP address and does not fall within
  * a private network range.
  *
  * @access public
  * @param string $ip
  */
 public function validate_ip($ip) {
     if (filter_var($ip, FILTER_VALIDATE_IP,
                         FILTER_FLAG_IPV4 |
                         FILTER_FLAG_IPV6 |
                         FILTER_FLAG_NO_PRIV_RANGE |
                         FILTER_FLAG_NO_RES_RANGE) === false)
         return false;
     self::$ip = $ip;
     return true;
 }

경고의 단어 (업데이트)

REMOTE_ADDR여전히 가장 안정적인 IP 주소 소스를 나타냅니다 . $_SERVER여기에 언급 된 다른 변수들은 원격 클라이언트에 의해 아주 쉽게 스푸핑 될 수 있습니다. 이 솔루션의 목적은 프록시 뒤에있는 클라이언트의 IP 주소를 확인하는 것입니다. 일반적으로이 주소를 직접 반환 한 IP 주소와 함께 사용하여 $_SERVER['REMOTE_ADDR']둘 다 저장하는 것을 고려할 수 있습니다 .

이 솔루션은 99.9 %의 사용자에게 완벽하게 적합합니다. 자체 요청 헤더를 주입하여 시스템을 악용하려는 악의적 인 사용자의 0.1 %로부터 사용자를 보호하지는 않습니다. 미션 크리티컬을 위해 IP 주소에 의존하는 경우 REMOTE_ADDR프록시 뒤에있는 사람들을 수용하고 귀찮게하지 마십시오.



답변

다음은 IP 주소를 얻는 더 짧고 깔끔한 방법입니다.

function get_ip_address(){
    foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key){
        if (array_key_exists($key, $_SERVER) === true){
            foreach (explode(',', $_SERVER[$key]) as $ip){
                $ip = trim($ip); // just to be safe

                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
                    return $ip;
                }
            }
        }
    }
}

도움이 되길 바랍니다!


귀하의 코드는 이미 완성 된 것으로 보이며 (일반적인 IP주의 사항을 제외하고) 가능한 버그를 볼 수 없으며 validate_ip()필터 확장에 의존 하도록 기능을 변경합니다 .

public function validate_ip($ip)
{
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false)
    {
        return false;
    }

    self::$ip = sprintf('%u', ip2long($ip)); // you seem to want this

    return true;
}

또한 HTTP_X_FORWARDED_FOR스 니펫은 다음과 같이 단순화 할 수 있습니다.

// check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
{
    // check if multiple ips exist in var
    if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false)
    {
        $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

        foreach ($iplist as $ip)
        {
            if ($this->validate_ip($ip))
                return $ip;
        }
    }

    else
    {
        if ($this->validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']))
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
}

이에:

// check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
{
    $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

    foreach ($iplist as $ip)
    {
        if ($this->validate_ip($ip))
            return $ip;
    }
}

IPv6 주소의 유효성을 검사 할 수도 있습니다.


답변

그럼에도 불구하고 사용자의 실제 IP 주소를 얻는 것은 신뢰할 수 없습니다. 익명 프록시 서버 (http_x_forwarded_for, http_forwarded 등의 헤더를 따르지 않는 서버) 만 사용하면 프록시 서버의 IP 주소 만 있으면됩니다.

그런 다음 익명의 프록시 서버 IP 주소 목록이 있는지 확인할 수 있지만 100 % 정확한지 확인할 수있는 방법은 없으며 프록시 서버임을 알 수 있습니다. 그리고 누군가가 영리하다면 HTTP 전달을 위해 헤더를 스푸핑 할 수 있습니다.

내가 지역 대학을 좋아하지 않는다고 가정 해 봅시다. 나는 당신이 어떤 IP 주소를 등록했는지 알아 내고, 당신이 HTTP 전달을 존중한다는 것을 알기 때문에 나쁜 일을함으로써 귀하의 사이트에서 그들의 IP 주소가 금지되도록합니다. 목록은 끝이 없습니다.

그리고 당신이 짐작했듯이, 내가 전에 언급했던 대학 네트워크와 같은 내부 IP 주소가 있습니다. 많은 것은 10.xxx 형식을 사용합니다. 공유 네트워크를 위해 전달되었다는 것입니다.

그런 다음에는 그다지 시작하지 않지만 동적 IP 주소는 더 이상 광대역의 길입니다. 그래서. 사용자 IP 주소를 얻더라도 가장 2-3 개월 안에 변경 될 것으로 예상하십시오.


답변

우리는 사용:

/**
 * Get the customer's IP address.
 *
 * @return string
 */
public function getIpAddress() {
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    } else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ips[count($ips) - 1]);
    } else {
        return $_SERVER['REMOTE_ADDR'];
    }
}

HTTP_X_FORWARDED_FOR의 폭발은 오징어 를 사용할 때 IP 주소를 감지하는 이상한 문제 때문입니다 .


답변

내 대답은 기본적으로 @AlixAxel의 대답의 세련되고 완벽하게 검증되고 완전히 패키지 된 버전입니다.

<?php

/* Get the 'best known' client IP. */

if (!function_exists('getClientIP'))
    {
        function getClientIP()
            {
                if (isset($_SERVER["HTTP_CF_CONNECTING_IP"]))
                    {
                        $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
                    };

                foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key)
                    {
                        if (array_key_exists($key, $_SERVER))
                            {
                                foreach (explode(',', $_SERVER[$key]) as $ip)
                                    {
                                        $ip = trim($ip);

                                        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
                                            {
                                                return $ip;
                                            };
                                    };
                            };
                    };

                return false;
            };
    };

$best_known_ip = getClientIP();

if(!empty($best_known_ip))
    {
        $ip = $clients_ip = $client_ip = $client_IP = $best_known_ip;
    }
else
    {
        $ip = $clients_ip = $client_ip = $client_IP = $best_known_ip = '';
    };

?>

변경 사항 :

  • 함수 이름을 단순화합니다 ( ‘camelCase’형식화 스타일 사용).

  • 함수가 코드의 다른 부분에서 이미 선언되지 않았는지 확인하는 검사가 포함되어 있습니다.

  • ‘CloudFlare’호환성을 고려합니다.

  • ‘getClientIP’함수의 여러 “IP 관련”변수 이름을 리턴 값으로 초기화합니다.

  • 함수가 유효한 IP 주소를 반환하지 않으면 모든 변수가 대신 빈 문자열로 설정됩니다 null.

  • 단지 45 줄의 코드입니다.


답변

가장 큰 문제는 어떤 목적을위한 것입니까?

귀하의 코드는 거의 포괄적입니다.하지만 프록시 추가 헤더처럼 보이는 것을 발견하면 CLIENT_IP의 INSTEAD를 사용하지만 감사 목적 으로이 정보를 원할 경우 매우 쉽습니다. 속이기 위해.

확실히 어떤 종류의 인증에도 IP 주소를 사용해서는 안됩니다. 스푸핑 될 수도 있습니다.

http가 아닌 포트를 통해 서버에 다시 연결되는 플래시 또는 자바 애플릿을 밀어 내면 클라이언트 IP 주소를 더 잘 측정 할 수 있습니다. 클라이언트가 웹 프록시를 통해서만 연결할 수 있거나 나가는 포트가 차단 된 경우 애플릿에서 연결되지 않습니다.

씨.


답변

나는 위에 더 좋고 더 간결한 답변이 있다는 것을 알고 있으며, 이것은 기능이나 가장 우아한 스크립트가 아닙니다. 우리의 경우 우리는 간단한 스위치 당 스푸핑 가능한 x_forwarded_for와보다 안정적인 remote_addr을 모두 출력해야했습니다. 사전 포맷 된 함수를 리턴하는 대신에 if-none 또는 if-singular 일 경우 다른 함수에 주입하기 위해 공백을 허용해야했습니다. 플랫폼 설정을 위해 스위치 별 사용자 정의 레이블이있는 “켜기 또는 끄기”변수가 필요했습니다. 또한 요청에 따라 $ ip를 동적으로 처리하여 forwarded_for 형식을 취할 수있는 방법이 필요했습니다.

또한 isset () vs! empty () 주소를 보지 못했습니다 .x_forwarded_for에 아무것도 입력하지 않아도 isset () 진리를 트리거하여 빈 var가 발생합니다. “PWNED”와 같은 단어를 x_forwarded_for로 스푸핑 할 수 있으므로 출력이 보호되거나 DB로 출력되는 경우 실제 IP 구문으로 멸균해야합니다.

또한 x_forwarder_for에서 배열을보기 위해 다중 프록시가 필요한 경우 Google Translate를 사용하여 테스트 할 수 있습니다. 테스트하기 위해 스푸핑 헤더를 원한다면 Chrome 클라이언트 헤더 스푸핑 확장 프로그램을 확인 하세요 . 이것은 기본 프록시 뒤에있는 표준 remote_addr로 기본 설정됩니다.

remote_addr이 비어있을 수있는 경우는 없지만, 경우에 따라 대체로 사용됩니다.

// proxybuster - attempts to un-hide originating IP if [reverse]proxy provides methods to do so
  $enableProxyBust = true;

if (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR'])) && (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
    $ip = end(array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']))));
    $ipProxy = $_SERVER['REMOTE_ADDR'];
    $ipProxy_label = ' behind proxy ';
} elseif (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR']))) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $ipProxy = '';
    $ipProxy_label = ' no proxy ';
} elseif (($enableProxyBust == false) && (isset($_SERVER['REMOTE_ADDR']))) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $ipProxy = '';
    $ipProxy_label = '';
} else {
    $ip = '';
    $ipProxy = '';
    $ipProxy_label = '';
}

로그 생성 또는 오류보고와 같이 아래의 함수 또는 쿼리 / 에코 / 뷰에 이러한 동적을 사용하려면 다른 조건이나 정적 스키마 출력을 만들지 않고 전역을 사용하거나 원하는 곳 어디에서나 em을 에코하십시오. 기능.

function fooNow() {
    global $ip, $ipProxy, $ipProxy_label;
    // begin this actions such as log, error, query, or report
}

당신의 모든 좋은 생각에 감사합니다. 이것이 더 나을 수 있는지 알려주십시오.이 헤더에 여전히 익숙합니다. 🙂


답변

IP 주소를 반환하는 것이 아니라 IP 정보가있는 배열을 반환하는이 기능을 생각해 냈습니다.

// Example usage:
$info = ip_info();
if ( $info->proxy ) {
    echo 'Your IP is ' . $info->ip;
} else {
    echo 'Your IP is ' . $info->ip . ' and your proxy is ' . $info->proxy_ip;
}

기능은 다음과 같습니다.

/**
 * Retrieves the best guess of the client's actual IP address.
 * Takes into account numerous HTTP proxy headers due to variations
 * in how different ISPs handle IP addresses in headers between hops.
 *
 * @since 1.1.3
 *
 * @return object {
 *         IP Address details
 *
 *         string $ip The users IP address (might be spoofed, if $proxy is true)
 *         bool $proxy True, if a proxy was detected
 *         string $proxy_id The proxy-server IP address
 * }
 */
function ip_info() {
    $result = (object) array(
        'ip' => $_SERVER['REMOTE_ADDR'],
        'proxy' => false,
        'proxy_ip' => '',
    );

    /*
     * This code tries to bypass a proxy and get the actual IP address of
     * the visitor behind the proxy.
     * Warning: These values might be spoofed!
     */
    $ip_fields = array(
        'HTTP_CLIENT_IP',
        'HTTP_X_FORWARDED_FOR',
        'HTTP_X_FORWARDED',
        'HTTP_X_CLUSTER_CLIENT_IP',
        'HTTP_FORWARDED_FOR',
        'HTTP_FORWARDED',
        'REMOTE_ADDR',
    );
    foreach ( $ip_fields as $key ) {
        if ( array_key_exists( $key, $_SERVER ) === true ) {
            foreach ( explode( ',', $_SERVER[$key] ) as $ip ) {
                $ip = trim( $ip );

                if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
                    $forwarded = $ip;
                    break 2;
                }
            }
        }
    }

    // If we found a different IP address then REMOTE_ADDR then it's a proxy!
    if ( $forwarded != $result->ip ) {
        $result->proxy = true;
        $result->proxy_ip = $result->ip;
        $result->ip = $forwarded;
    }

    return $result;
}