วิธีที่ถูกต้องที่สุดในการดึงที่อยู่ IP ที่ถูกต้องของผู้ใช้ใน PHP คืออะไร?


301

ฉันรู้ว่ามีตัวแปร$ _SERVERส่วนหัวที่พร้อมใช้งานสำหรับการดึงข้อมูลที่อยู่ IP ฉันสงสัยว่ามีมติทั่วไปว่าจะดึงข้อมูลที่อยู่ 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และไม่ใส่ใจกับผู้ที่อยู่เบื้องหลังพรอกซี


2
สำหรับคำถาม whatismyip.com ฉันคิดว่าพวกเขาทำอะไรบางอย่างเช่นสคริปต์นี้ หากคุณเป็นเช่นนั้นเพราะเหตุใดคุณจึงมี IP ภายในจะไม่มีสิ่งใดถูกส่งผ่านอินเทอร์เฟซสาธารณะในกรณีนี้ดังนั้นจึงไม่มีข้อมูลสำหรับ PHP ที่จะได้รับ
Matt

2
ตรวจสอบให้แน่ใจว่าคุณคำนึงถึงเรื่องนี้เมื่อใช้สิ่งนี้: stackoverflow.com/questions/1672827/…
Kevin Peno

19
โปรดจำไว้ว่าส่วนหัว HTTP ทั้งหมดเหล่านี้แก้ไขได้ง่ายมาก: ด้วยโซลูชันของคุณฉันต้องกำหนดค่าเบราว์เซอร์ของฉันให้ส่งส่วนหัว X-Forwarded-For สำหรับด้วย IP แบบสุ่มและสคริปต์ของคุณจะส่งคืนที่อยู่ปลอมอย่างมีความสุข ดังนั้นขึ้นอยู่กับสิ่งที่คุณพยายามทำวิธีนี้อาจเชื่อถือได้น้อยกว่าเพียงแค่ใช้ REMOTE_ADDR
gnomnain

14
OMFG "ip ที่ไม่น่าเชื่อถือ"! ครั้งแรกที่ฉันเห็นเรื่องไร้สาระเช่นนี้ที่นี่ ที่อยู่ IP ที่เชื่อถือได้เพียง REMOTE_ADDR
สามัญสำนึกของคุณ

3
-1 สิ่งนี้มีความเสี่ยงต่อการปลอมแปลง สิ่งที่คุณทำคือถามผู้ใช้ว่าที่อยู่ IP ของเขาควรเป็นอะไร
rook

คำตอบ:


268

นี่เป็นวิธีที่สั้นกว่าและสะอาดกว่าในการรับที่อยู่ 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 caveats ปกติ) ฉันจะเปลี่ยน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


4
ฉันขอขอบคุณการfilter_varแก้ไขอย่างแน่นอนเนื่องจากจะลบการตรวจสอบเครือข่ายที่ไม่ได้ลงทะเบียนที่อยู่ IP ที่ไม่ได้ลงชื่อ ฉันยังชอบความจริงที่ทำให้ฉันมีตัวเลือกในการตรวจสอบที่อยู่ IPv6 เช่นกัน การHTTP_X_FORWARDED_FORเพิ่มประสิทธิภาพยังชื่นชมมาก ในไม่กี่นาทีฉันจะอัปเดตรหัส
Corey Ballou

33
-1 สิ่งนี้มีความเสี่ยงที่จะปลอมแปลงสิ่งที่คุณกำลังทำอยู่คือถามผู้ใช้ว่าที่อยู่ IP ของเขาควรเป็นอะไร
rook

7
@ โกง: ใช่ฉันรู้ OP ตระหนักถึงสิ่งนั้นและฉันได้กล่าวถึงมันในคำตอบของฉันด้วย แต่ขอบคุณสำหรับความคิดเห็น
Alix Axel

1
FYI: ฉันต้องลบ FILTER_FLAG_IPV6 เพื่อให้รหัสของ Alix Axel ทำงานได้
darkAsPitch

2
@ rubenrp81 ตัวจัดการซ็อกเก็ต TCP เป็นแหล่งบัญญัติเดียวซึ่งทุกอย่างควบคุมโดยผู้โจมตี โค้ดด้านบนเป็นความฝันของผู้โจมตี
rook

12

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

จากนั้นคุณสามารถดูว่ามีรายการของที่อยู่ IP ของพร็อกซีเซิร์ฟเวอร์ที่ไม่ระบุตัวตนหรือไม่ แต่ไม่มีวิธีที่จะตรวจสอบให้แน่ใจว่ามีความถูกต้อง 100% เช่นกันและส่วนใหญ่ที่ทำเช่นนั้นคือแจ้งให้คุณทราบว่าเป็น และถ้าใครฉลาดก็สามารถหลอกส่วนหัวสำหรับ HTTP ไปข้างหน้า

สมมติว่าฉันไม่ชอบวิทยาลัยท้องถิ่น ฉันเข้าใจว่าพวกเขาลงทะเบียนที่อยู่ IP ใดและรับที่อยู่ IP ของพวกเขาบนไซต์ของคุณโดยการทำสิ่งที่ไม่ดี รายการไม่มีที่สิ้นสุด

จากนั้นก็มีอย่างที่คุณคาดเดาได้ว่าที่อยู่ IP ภายในเช่นเครือข่ายวิทยาลัยที่ฉันเคยพบมาก่อน มากใช้รูปแบบ 10.xxx ดังนั้นคุณจะรู้ว่ามันถูกส่งต่อไปยังเครือข่ายที่ใช้ร่วมกัน

ถ้าเช่นนั้นฉันจะไม่เริ่มต้นอะไรมากมาย แต่ที่อยู่ IP แบบไดนามิกเป็นวิธีการบรอดแบนด์อีกต่อไป ดังนั้น. แม้ว่าคุณจะได้รับที่อยู่ IP ของผู้ใช้คาดหวังว่ามันจะเปลี่ยนใน 2 - 3 เดือนที่ยาวที่สุด


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

@cballou - แท้จริงแล้วสำหรับวัตถุประสงค์นี้ REMOTE_ADDR เป็นสิ่งที่ถูกต้องที่จะใช้ วิธีใดก็ตามที่ใช้ส่วนหัว HTTP จะมีความเสี่ยงต่อการปลอมแปลงส่วนหัว เซสชันใช้เวลานานเท่าใด IP แบบไดนามิกไม่เปลี่ยนแปลงอย่างรวดเร็ว
MZB

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

8

เราใช้:

/**
 * 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 เมื่อปลาหมึกถูกนำมาใช้


อ๊ะฉันเพิ่งรู้ว่าคุณทำสิ่งเดียวกันโดยมีการระเบิดและอื่น ๆ บวกเพิ่มอีกเล็กน้อย ดังนั้นฉันสงสัยว่าคำตอบของฉันช่วยได้มาก :)
gabrielk

ส่งกลับที่อยู่ของ localhost
Scarl

3

คำตอบของฉันนั้นเป็นเพียงคำตอบของ @ 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'

  • เริ่มต้นชื่อตัวแปร "ที่เกี่ยวข้องกับ IP" หลายรายการเป็นค่าที่ส่งคืนของฟังก์ชัน 'getClientIP'

  • ทำให้มั่นใจได้ว่าถ้าฟังก์ชั่นไม่ได้กลับที่อยู่ IP nullที่ถูกต้องตัวแปรทั้งหมดที่มีการตั้งค่าให้เป็นสตริงที่ว่างเปล่าแทน

  • มันเป็นโค้ด (45) บรรทัดเท่านั้น


2

คำถามที่ใหญ่ที่สุดสำหรับวัตถุประสงค์อะไร

รหัสของคุณครอบคลุมเกือบเท่าที่ควร - แต่ฉันเห็นว่าถ้าคุณเห็นสิ่งที่ดูเหมือนว่าส่วนหัวของพร็อกซี่ที่เพิ่มเข้ามาคุณใช้ INSTEAD ของ CLIENT_IP อย่างไรก็ตามหากคุณต้องการข้อมูลนี้เพื่อวัตถุประสงค์ในการตรวจสอบแล้ว - ง่ายมาก เพื่อปลอม

แน่นอนว่าคุณไม่ควรใช้ที่อยู่ IP สำหรับการตรวจสอบความถูกต้องประเภทใด ๆ - แม้สิ่งเหล่านี้สามารถปลอมแปลงได้

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

ค.


เมื่อคำนึงถึงฉันกำลังมองหาโซลูชัน PHP เท่านั้นคุณแนะนำให้ฉันเพิ่ม$_SERVER['CLIENT_IP']เป็นอย่างอื่นถ้าคำสั่ง?
Corey Ballou

ไม่ - แค่นั้นถ้าคุณต้องการให้ความสำคัญกับข้อมูลที่ส่งคืนมันก็เป็นความคิดที่ดีที่จะรักษาที่อยู่ปลายทางของเครือข่าย (IP ของลูกค้า) รวมถึงสิ่งใดก็ตามที่แนะนำค่าที่แตกต่างในพร็อกซีที่เพิ่มส่วนหัว (เช่นคุณ ดูที่อยู่ 192.168.1.x จำนวนมาก แต่มาจากไคลเอนต์ IPS ที่แตกต่างกัน) C.
symcbean

1

ฉันรู้ว่ามีคำตอบที่ดีกว่าและสั้นลงกว่านี้และนี่ไม่ใช่ฟังก์ชั่นหรือสคริปต์ที่สง่างามที่สุด ในกรณีของเราเราจำเป็นต้องส่งออกทั้ง x_forwarded_for spoofable และ remote_addr ที่เชื่อถือได้มากขึ้นในสวิตช์แบบง่ายต่อการพูด จำเป็นต้องอนุญาตให้มีช่องว่างสำหรับการฉีดเข้าไปในฟังก์ชั่นอื่น ๆ if-none หรือ if-singular (แทนที่จะเพียงแค่คืนค่าฟังก์ชั่นที่ฟอร์แมตแล้ว) ต้องการ var "on or off" พร้อมป้ายกำกับแบบกำหนดเองสำหรับแต่ละสวิตช์สำหรับการตั้งค่าแพลตฟอร์ม นอกจากนี้ยังต้องการวิธีสำหรับ $ ip ที่จะเป็นแบบไดนามิกขึ้นอยู่กับคำขอเพื่อที่จะใช้รูปแบบของ forwarded_for

นอกจากนี้ฉันไม่เห็นใครที่อยู่ isset () vs! empty () - มันเป็นไปได้ที่จะไม่ป้อนอะไรเลยสำหรับ x_forwarded_for แต่ยังคงทริกเกอร์ isset () ความจริงส่งผลให้ var ว่างเปล่าวิธีการหลีกเลี่ยงคือการใช้ && และรวมทั้งเงื่อนไข โปรดทราบว่าคุณสามารถปลอมคำเช่น "PWNED" เป็น x_forwarded_for ดังนั้นให้แน่ใจว่าคุณทำหมันให้เป็นไวยากรณ์ ip จริงถ้าผลลัพธ์ของคุณได้รับการป้องกันหรือเข้าไปในฐานข้อมูล

นอกจากนี้คุณยังสามารถทดสอบโดยใช้ Google แปลถ้าคุณต้องการหลายพร็อกซีเพื่อดูอาร์เรย์ใน x_forwarder_for หากคุณต้องการหัวหมุนเพื่อทดสอบตรวจสอบส่วนขยายการปลอมแปลงส่วนหัวของไคลเอ็นต์ Chrome สิ่งนี้จะใช้ค่าเริ่มต้นเป็น remote_addr มาตรฐานเพียงอย่างเดียวขณะอยู่หลัง anon proxy

ฉันไม่ทราบว่ากรณีใด ๆ ที่ 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 = '';
}

ในการทำให้ไดนามิกเหล่านี้สำหรับใช้ในฟังก์ชั่นหรือเคียวรี / echo / มุมมองด้านล่างพูดสำหรับ log gen หรือการรายงานข้อผิดพลาดใช้ globals หรือเพียงแค่ echo em ในทุกที่ที่คุณต้องการโดยไม่ต้องทำเงื่อนไขอื่น ๆ ฟังก์ชั่น.

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

ขอบคุณสำหรับความคิดที่ดีทั้งหมดของคุณ โปรดแจ้งให้เราทราบหากสิ่งนี้ดีกว่านี้ยังคงเป็นเรื่องใหม่สำหรับส่วนหัวเหล่านี้ :)


1

ฉันมากับฟังก์ชั่นนี้ที่ไม่เพียงแค่คืนค่าที่อยู่ 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;
}

1

ดังที่มีคนพูดไว้ก่อนหน้านี้กุญแจที่อยู่นี้มีเหตุผลอะไรที่คุณต้องการจัดเก็บรหัสผู้ใช้

ฉันจะยกตัวอย่างจากระบบการลงทะเบียนที่ฉันทำงานและแน่นอนวิธีแก้ปัญหาเพียงเพื่อให้มีส่วนร่วมในการสนทนาเก่านี้ที่มาบ่อยครั้งในการค้นหาของฉัน

ไลบรารีการลงทะเบียน php จำนวนมากใช้ipเพื่อเค้น / ปิดใช้งานความพยายามที่ล้มเหลวโดยอ้างอิงจาก IP ของผู้ใช้ พิจารณาตารางนี้:

-- mysql
DROP TABLE IF EXISTS `attempts`;
CREATE TABLE `attempts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ip` varchar(39) NOT NULL, /*<<=====*/
  `expiredate` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 -- sqlite
...

จากนั้นเมื่อผู้ใช้พยายามทำการเข้าสู่ระบบหรือสิ่งที่เกี่ยวข้องกับการบริการเช่นการรีเซ็ตรหัสผ่านฟังก์ชั่นจะถูกเรียกเมื่อเริ่มต้น:

public function isBlocked() {
      /*
       * used one of the above methods to capture user's ip!!!
       */
      $ip = $this->ip;
      // delete attempts from this ip with 'expiredate' in the past
      $this->deleteAttempts($ip, false);
      $query = $this->dbh->prepare("SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ?");
      $query->execute(array($ip));
      $attempts = $query->fetchColumn();
      if ($attempts < intval($this->token->get('attempts_before_verify'))) {
         return "allow";
      }
      if ($attempts < intval($this->token->get('attempts_before_ban'))) {
         return "captcha";
      }
      return "block";
   }

ตัวอย่างเช่นสมมติว่ามี$this->token->get('attempts_before_ban') === 10ผู้ใช้ 2 รายที่มาจากรหัสเดียวกันกับที่เป็นรหัสก่อนหน้าซึ่งสามารถทำการปลอมแปลงส่วนหัวได้หลังจาก 5 ครั้งแต่ละครั้งจะถูกแบน ! แม้แต่ที่เลวร้ายที่สุดถ้าทั้งหมดมาจากพรอกซีเดียวกันผู้ใช้ 10 คนแรกเท่านั้นที่จะถูกบันทึกและส่วนที่เหลือทั้งหมดจะถูกแบน!

สิ่งสำคัญที่นี่คือเราต้องการดัชนีที่ไม่ซ้ำใครบนโต๊ะattemptsและเราสามารถหาได้จากชุดค่าผสมเช่น:

 `ip` varchar(39) NOT NULL,
 `jwt_load varchar(100) NOT NULL

ที่jwt_loadมาจากคุกกี้ http ที่ตามหลังเทคโนโลยีjson web tokenที่เราเก็บเฉพาะส่วนของข้อมูลที่เข้ารหัสซึ่งควรมีค่าที่ไม่ซ้ำกัน / ไม่ซ้ำกันสำหรับผู้ใช้ทุกคน แน่นอนควรเปลี่ยนเป็น: "SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ? AND jwt_load = ?"และคลาสควรเริ่มต้นprivate $jwtด้วย


0

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


1
ต้องอ่านหน้าวิกิพีเดียใน HTTP_X_FORWARDED_FOR: en.wikipedia.org/wiki/X-Forwarded- สำหรับ ... ฉันเห็นคำสั่งที่แนะนำคือจากซ้ายไปขวาตามรหัสของคุณ อย่างไรก็ตามจากบันทึกของเราฉันสามารถดูได้ว่ามีหลายกรณีที่ผู้รับมอบฉันทะไม่เคารพใน wild และที่อยู่ IP ที่คุณต้องการตรวจสอบอาจอยู่ที่ท้ายรายการ
Chris Withers

1
หรือตรงกลางเช่นเดียวกับที่จะเกิดขึ้นหากผู้รับมอบฉันทะบางคนเคารพคำสั่งซื้อจากซ้ายไปขวาและอื่น ๆ ไม่ได้
Brilliand

0

ขอบคุณสำหรับสิ่งนี้มีประโยชน์มาก

มันจะช่วยได้แม้ว่ารหัสนั้นถูกต้องหรือไม่ เนื่องจากมี {จำนวนมากเกินไปประมาณ 20 บรรทัดซึ่งฉันกลัวหมายความว่าไม่มีใครลองใช้งานจริง

ฉันอาจบ้า แต่หลังจากลองใช้ที่อยู่ที่ถูกต้องและไม่ถูกต้องเพียงสองรุ่น validate_ip () ที่ทำงานได้คือ:

    public function validate_ip($ip)
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false)
            return false;
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) === false)
            return false;
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false)
            return false;

        return true;
    }

0

นี่คือเวอร์ชันที่แก้ไขหากคุณใช้บริการแคCloudFlare layer

function getIP()
{
    $fields = array('HTTP_X_FORWARDED_FOR',
                    'REMOTE_ADDR',
                    'HTTP_CF_CONNECTING_IP',
                    'HTTP_X_CLUSTER_CLIENT_IP');

    foreach($fields as $f)
    {
        $tries = $_SERVER[$f];
        if (empty($tries))
            continue;
        $tries = explode(',',$tries);
        foreach($tries as $try)
        {
            $r = filter_var($try,
                            FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 |
                            FILTER_FLAG_NO_PRIV_RANGE |
                            FILTER_FLAG_NO_RES_RANGE);

            if ($r !== false)
            {
                return $try;
            }
        }
    }
    return false;
}

0

เพียงแค่คำตอบในเวอร์ชันVB.NET :

Private Function GetRequestIpAddress() As IPAddress
    Dim serverVariables = HttpContext.Current.Request.ServerVariables
    Dim headersKeysToCheck = {"HTTP_CLIENT_IP", _
                              "HTTP_X_FORWARDED_FOR", _
                              "HTTP_X_FORWARDED", _
                              "HTTP_X_CLUSTER_CLIENT_IP", _
                              "HTTP_FORWARDED_FOR", _
                              "HTTP_FORWARDED", _
                              "REMOTE_ADDR"}
    For Each thisHeaderKey In headersKeysToCheck
        Dim thisValue = serverVariables.Item(thisHeaderKey)
        If thisValue IsNot Nothing Then
            Dim validAddress As IPAddress = Nothing
            If IPAddress.TryParse(thisValue, validAddress) Then
                Return validAddress
            End If
        End If
    Next
    Return Nothing
End Function

3
มีแท็ก "PHP" ในคำถาม
luchaninov

0

อีกวิธีที่สะอาด:

  function validateIp($var_ip){
    $ip = trim($var_ip);

    return (!empty($ip) &&
            $ip != '::1' &&
            $ip != '127.0.0.1' &&
            filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
            ? $ip : false;
  }

  function getClientIp() {
    $ip = @$this->validateIp($_SERVER['HTTP_CLIENT_IP']) ?:
          @$this->validateIp($_SERVER['HTTP_X_FORWARDED_FOR']) ?:
          @$this->validateIp($_SERVER['HTTP_X_FORWARDED']) ?:
          @$this->validateIp($_SERVER['HTTP_FORWARDED_FOR']) ?:
          @$this->validateIp($_SERVER['HTTP_FORWARDED']) ?:
          @$this->validateIp($_SERVER['REMOTE_ADDR']) ?:
          'LOCAL OR UNKNOWN ACCESS';

    return $ip;
  }

0

จากคลาสคำขอของ Symfony https://github.com/symfony/symfony/blob/1bd125ec4a01220878b3dbc3ec3156b073996af9/src/Symfony/Component/HttpFoundation/Rttest

const HEADER_FORWARDED = 'forwarded';
const HEADER_CLIENT_IP = 'client_ip';
const HEADER_CLIENT_HOST = 'client_host';
const HEADER_CLIENT_PROTO = 'client_proto';
const HEADER_CLIENT_PORT = 'client_port';

/**
 * Names for headers that can be trusted when
 * using trusted proxies.
 *
 * The FORWARDED header is the standard as of rfc7239.
 *
 * The other headers are non-standard, but widely used
 * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
 */
protected static $trustedHeaders = array(
    self::HEADER_FORWARDED => 'FORWARDED',
    self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
    self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
    self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
    self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
);

/**
 * Returns the client IP addresses.
 *
 * In the returned array the most trusted IP address is first, and the
 * least trusted one last. The "real" client IP address is the last one,
 * but this is also the least trusted one. Trusted proxies are stripped.
 *
 * Use this method carefully; you should use getClientIp() instead.
 *
 * @return array The client IP addresses
 *
 * @see getClientIp()
 */
public function getClientIps()
{
    $clientIps = array();
    $ip = $this->server->get('REMOTE_ADDR');
    if (!$this->isFromTrustedProxy()) {
        return array($ip);
    }
    if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
        $forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
        preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
        $clientIps = $matches[3];
    } elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
        $clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
    }
    $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
    $firstTrustedIp = null;
    foreach ($clientIps as $key => $clientIp) {
        // Remove port (unfortunately, it does happen)
        if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
            $clientIps[$key] = $clientIp = $match[1];
        }
        if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
            unset($clientIps[$key]);
        }
        if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
            unset($clientIps[$key]);
            // Fallback to this when the client IP falls into the range of trusted proxies
            if (null ===  $firstTrustedIp) {
                $firstTrustedIp = $clientIp;
            }
        }
    }
    // Now the IP chain contains only untrusted proxies and the client IP
    return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
}

คุณสมบัติที่ไม่ได้กำหนด: $ เซิร์ฟเวอร์
C47

0

ฉันประหลาดใจที่ไม่มีใครพูดถึง filter_input ดังนั้นนี่คือคำตอบของ Alix Axel ที่รวมอยู่ในบรรทัดเดียว:

function get_ip_address(&$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'])
{
    return empty($keys) || ($ip = filter_input(INPUT_SERVER, array_pop($keys), FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))? $ip : get_ip_address($keys);
}

-1

คุณตอบคำถามของคุณเองแล้ว! :)

function getRealIpAddr() {
    if(!empty($_SERVER['HTTP_CLIENT_IP']))   //Check IP address from shared Internet
    {
        $IPaddress = $_SERVER['HTTP_CLIENT_IP'];
    }
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))   //To check IP address is passed from the proxy
    {
        $IPaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    else
    {
        $IPaddress = $_SERVER['REMOTE_ADDR'];
    }
    return $IPaddress;
}

แหล่ง


-6
/**
 * Sanitizes IPv4 address according to Ilia Alshanetsky's book
 * "php|architect?s Guide to PHP Security", chapter 2, page 67.
 *
 * @param string $ip An IPv4 address
 */
public static function sanitizeIpAddress($ip = '')
{
if ($ip == '')
    {
    $rtnStr = '0.0.0.0';
    }
else
    {
    $rtnStr = long2ip(ip2long($ip));
    }

return $rtnStr;
}

//---------------------------------------------------

/**
 * Returns the sanitized HTTP_X_FORWARDED_FOR server variable.
 *
 */
public static function getXForwardedFor()
{
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
    {
    $rtnStr = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
elseif (isset($HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR']))
    {
    $rtnStr = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'];
    }
elseif (getenv('HTTP_X_FORWARDED_FOR'))
    {
    $rtnStr = getenv('HTTP_X_FORWARDED_FOR');
    }
else
    {
    $rtnStr = '';
    }

// Sanitize IPv4 address (Ilia Alshanetsky):
if ($rtnStr != '')
    {
    $rtnStr = explode(', ', $rtnStr);
    $rtnStr = self::sanitizeIpAddress($rtnStr[0]);
    }

return $rtnStr;
}

//---------------------------------------------------

/**
 * Returns the sanitized REMOTE_ADDR server variable.
 *
 */
public static function getRemoteAddr()
{
if (isset($_SERVER['REMOTE_ADDR']))
    {
    $rtnStr = $_SERVER['REMOTE_ADDR'];
    }
elseif (isset($HTTP_SERVER_VARS['REMOTE_ADDR']))
    {
    $rtnStr = $HTTP_SERVER_VARS['REMOTE_ADDR'];
    }
elseif (getenv('REMOTE_ADDR'))
    {
    $rtnStr = getenv('REMOTE_ADDR');
    }
else
    {
    $rtnStr = '';
    }

// Sanitize IPv4 address (Ilia Alshanetsky):
if ($rtnStr != '')
    {
    $rtnStr = explode(', ', $rtnStr);
    $rtnStr = self::sanitizeIpAddress($rtnStr[0]);
    }

return $rtnStr;
}

//---------------------------------------------------

/**
 * Returns the sanitized remote user and proxy IP addresses.
 *
 */
public static function getIpAndProxy()
{
$xForwarded = self::getXForwardedFor();
$remoteAddr = self::getRemoteAddr();

if ($xForwarded != '')
    {
    $ip    = $xForwarded;
    $proxy = $remoteAddr;
    }
else
    {
    $ip    = $remoteAddr;
    $proxy = '';
    }

return array($ip, $proxy);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.