【问题标题】:Match a whitelist of IP addresses in PHP匹配PHP中的IP地址白名单
【发布时间】:2016-09-09 20:39:49
【问题描述】:

我有一个 IP 范围列表,例如

$whitelist=array('50*','202.16*','123.168*',');

我想阻止所有其他流量查看该页面。

我试过了

 if(in_array($_SERVER['REMOTE_ADDR'],$whitelist)){
    //display page
    }

【问题讨论】:

  • 那么什么是“不”工作?
  • 来自已批准 IP 地址的访问者看不到该页面
  • 我之前成功使用过类似的东西。首先,您需要获取他们的服务器地址$ip = $_SERVER['REMOTE_ADDR'];,然后在点上展开,并将其分配给$if,并检查$if 和黑/白名单。
  • 您在下面有几个答案。我会采用不同的方法,但我认为你已经足够了 ;-)

标签: php regex network-programming ip


【解决方案1】:

in_array 不使用正则表达式进行比较。此外,您的正则表达式不正确,* 是一个量词。这允许零个或多个前一个字符。

试试:

$whitelist=array('50\..*','202\.16\..*','123\.168\..*');
if(preg_match('/^(' . implode('|', $whitelist) . ')/', $_SERVER['REMOTE_ADDR'])){

.* 允许任何内容(几乎(参见 s 修饰符 http://php.net/manual/en/reference.pcre.pattern.modifiers.php),. 是任何字符,然后与前面提到的量词配对)。
^ 是开始字符串。
\. 是文字 .
| 是一个或。

演示:https://eval.in/571019
正则表达式演示:https://regex101.com/r/dC5uI0/1

【讨论】:

  • 我开始认为我应该用这个替换我一直在使用的那个 lol 它更强大。
  • @Fred-ii- 你的过去/现在是什么?
  • 我的是根据我在 OP 问题下留下的评论。它使用从服务器地址分解的{$var[0]}.{$var[1]}.{$var[2]},然后检查它是否存在于数组中,但是在检查第一个元素时效率低下,因为我必须减少或添加到数组中。它有效,但您的方法更容易使用。
  • 注意,使用正则表达式时,IP的点需要转义:192.168.0.2 --> 192\.168\.0\.2
  • @luckydonald 答案指出,在解释“\. is a literal .”和. 是任何单个字符的声明中......并且在代码本身中每个. 都被转义
【解决方案2】:

这应该适合你:

您可以循环访问白名单 ip 并修剪空间和 *(如果从右侧找到)。

在使用substr之后可以循环切掉相同长度的白名单ip的IP地址,比较一下。

$whitelists = array('50*','202.16*','123.168*');
foreach($whitelists as $whitelist){
    $whitelist = rtrim($whitelist, "*\r\n\t\0 ");
    if(substr($_SERVER['REMOTE_ADDR'], 0, strlen($whitelist)) == $whitelist) {
        $match = true;
        break;
    }
}
echo $match ? 'Match' : 'Not Match';

【讨论】:

    【解决方案3】:

    如@chris85 所述,in_array 不使用正则表达式。

    要做这样的事情,你可以简单地使用这样的循环:

    if(preg_match('/^(' . implode('|', $whitelist) . ')/i', $_SERVER['REMOTE_ADDR'])){
    // Do your stuff
    
    }
    

    你的'*'不像你想象的那样工作..没关系:

    $whitelist=array('50\.*','202.16\.*','123.168\.*');
    

    【讨论】:

    • 那么为什么重复的答案和对克里斯的答案的捎带?我所看到的不同之处在于添加了i
    【解决方案4】:

    希望这对某人有所帮助。鉴于您没有具体询问正则表达式,并且由于主题是关于匹配 IP 地址,我想我会把它放在那里,以便它可以帮助遇到类似问题的人。

    服务器软件通常力求尽可能快速和高效;匹配 IP 地址通常以算术方式完成。话虽如此,在讨论可能的替代方案之前,我将介绍一种快速的方法来完全按照您的要求进行操作。

    如果您只是对 IP 地址字符串执行通配符匹配,我建议您使用此方法。它已根据您的用例量身定制,但我将自己包含简单的匹配功能。

    相比之下,与使用 PHP 的 RegEx 函数(这是更复杂的方法模式匹配)

    注意事项:

    • 我将此处引用的函数用于非常特定的目的。它们适用于这种情况,因为 IP 地址中没有“*”字符。正如它们所写的那样,如果您正在测试的变量中包含“*”字符,它只会与通配符匹配,因此存在丢失信息的可能性很小。
    • 如果您正在编写命令行守护程​​序,或者您的进程将在其生命周期内使用相同的 IP 列表进行多次检查,那么使用 RegEx 库是有益的。只有在必须初始加载和准备 IP 列表 RegEx 以供首次使用时,才能在此处使用我的方法带来的小幅速度优势。
    • 您可以将代码从“wildcard_match()”移动到“match_ip()”内部以获得更多好处,避免另一个函数调用的开销。

    代码(可以复制粘贴):

    <?php 
    /**
     * This function compares a string ("$test") to see if it is
     * equal to another string ("$wild").  An '*' in "$wild" will 
     * match any characters in "$test".
     *
     * @param string $wild - The string to compare against.  This may
     * be either an exact character string to match, or a string
     * with a wild card ('*') character that will match any character(s) 
     * found in "$test".
     * 
     * @param string $test - A character string we're comparing against
     * "$wild" to determine if there is a match.
     * 
     * @return bool Returns TRUE if "$test" is either an exact match to
     * "$wild", or it fits the bill taking any wild card characters into
     * consideration.
     * 
     **/ 
    function wildcard_match( $pattern, $test ) {
        $p = 0;
        $a_name = explode("*", $pattern);
        $segs = count($a_name);
        $max_seg = ($segs-1);
        $plen = 0;
        $test_len = strlen($test);
        for ($i = 0; $i < $segs; $i++) {
            $part = $a_name[$i];
            $plen = strlen($part);
            if ($plen === 0) {
                if ($i === $max_seg) return true;
                continue;
            }
            $p = strpos($test, $part, $p);
            if ($p === false) {
                return false;
            }
            $p+=$plen;
        }
        if ($p===$test_len) {
            return true;
        }
        return false;
    }
    /**
     * Function to quickly traverse an array of whole, or 
     * wild card IPv4 addresses given in "$whitelist" and
     * determine if they match the given whole IPv4 
     * address in "$test".
     *
     * @param array $whitelist  - An array of IPv4 addresses, either
     * whole, or containing an '*' character wherein any character(s)
     * in "$test" will match.
     *
     * @param string $test      - A complete string (dot-decimal) IPv4
     * address to compare against the contents of the array given in
     * parameter one ("$whitelist"). 
     *
     * @return bool Returns TRUE, if the IPv4 address given in "$test" was
     * matched to an IPv4 address or IPv4 wild card pattern in the array
     * given in parameter one ("$whitelist").
     *
     **/
    function match_ip( $whitelist, $test ) {
        foreach ($whitelist as $w) {
            if (wildcard_match($w, $test)) return true;
        }
        return false;
    }
    
        /* The array of IP addresses we're going to validate */
        $check_array = array("50.245.1.9", "35.125.25.255", "202.16.15.25");
    
        /* The array as given in your example (minus the extra ' at the end) */
        $whitelist1=array('50*','202.16*','123.168*');
    
        /* An array for RegEx matching */
        $whitelist2=array('50\..*','202\.16\..*','123\.168\..*');
    
        microtime(true); /* Execute this once to make sure its module is loaded */
        echo "Giving PHP a second to get its ducks in a row...\n";
        usleep(1000000); /** Give PHP a second to load and prepare */
    
    
        $st = microtime(true);
        foreach ($check_array as $c) {
            if (match_ip($whitelist1, $c)) {
                echo "$c....Match!\n";
            } else {
                echo "$c....No match!\n";
            }
        }
        $dt = microtime(true)-$st;
        echo "Time: $dt\n\n\n\n";
    
    
        $st = microtime(true);
        foreach ($check_array as $c) {
            if(preg_match('/^(' . implode('|', $whitelist2) . ')/', $c)){
                echo "$c....Match!\n";
            } else {
                echo "$c....No Match!\n";
            }
        }
        $dt = microtime(true)-$st;
        echo "Time: $dt\n\n";
    

    由此产生的输出是:

    Giving PHP a second to get its ducks in a row...
    50.245.1.9....Match!
    35.125.25.255....No match!
    202.16.15.25....Match!
    Time 1: 0.00027704238891602
    
    50.245.1.9....Match!
    35.125.25.255....No Match!
    202.16.15.25....Match!
    Time 2: 0.00040698051452637
    

    第一个结果集来自函数“match_ip()”,第二个结果集来自启动 RegEx 库。

    现在,对于通配符匹配 IP,一个可能更好的解决方案是在您的数组中使用 CIDR 表示法的 IP 地址数组。通常,您希望允许来自特定网络或 IP 地址范围的 IP 流量。

    这里有很多假设,但例如(使用您的“$whitelist”数组):

    '50*' 可能被解释为“我希望允许来自 50.xxx.xxx.xxx 的所有 IP 地址访问。

    在这种情况下,您需要指定格式“50.0.0.0/8”。 (“50”后面的“0”可以是任意数字。因为“/8”,它们将被完全忽略。)

    xxx.xxx.xxx.xxx 
     |   |   |   |
     8   16  24  32
    

    IPv4 地址在计算上是 32 位,所以在上面,您说您只关心前 8 位匹配。

    “123.168*”将是“123.168.0.0/16”

    “101.23.54.0/24”将允许所有以“101.23.54”开头的IP地址访问。

    “44.32.240.10/32”将允许 IP 地址“44.32.240.10”访问。没有范围。

    所以你可以这样做:

    <?php 
    
    /**
     * Determines if the two given IPv4 addresses 
     * are equal, or are on the same network using
     * the given number of "$mask" bits.
     * 
     * @param string $ip1  - The first string dot-decimal IPv4
     * address.
     * 
     * @param string $ip1  - The second string dot-decimal IPv4
     * address.
     * 
     * @param int $mask    - The number of bits in the mask.
     * 
     * @return bool Returns TRUE if they match after being
     * masked, or FALSE if not.
     * 
     */
    function ip_match( $ip1, $ip2, $mask) {
        $mask = (int)$mask;
        $ip1  = ip2long($ip1);
        $ip2  = ip2long($ip2);
        if ($ip1 === false || $ip2 === false) return false;
        if ($mask < 1 || $mask > 32) return false;
        $mask =  (0x00000000FFFFFFFF & 0x00000000FFFFFFFF << (32-$mask));
        if ( ($ip1 & $mask) === ($ip2 & $mask) ) {
            return true;
        }
        return false;
    }
    /**
     * Takes an array of string (CIDR) network representations and 
     * sorts them into an array used later for checking against IP
     * addresses.
     * 
     * @param array $cidr_array - An array of IP addressess in
     * CIDR notation e.g. 192.168.1.1/24
     * 
     * @return array Returns an array of objects with the following
     * properties:
     *      'ip'   - The string (dot-decimal) IP address that
     *               has been numerically verified for use 
     *               in comparisons later.
     * 
     *      'mask' - The number of bits used for creating 
     *               the subnet mask against which IP 
     *               addresses will be compared.
     * 
     **/
    function make_whitelist( $cidr_array ) {
        $wl   = array();
        $lip  = 0;
        $bm   = 0;
        $spos = 0;
        if (!is_array($cidr_array)) return false;
        foreach ($cidr_array as $ip) {
            $spos = strpos($ip, "/");
            if ($spos === false) {
                $bm = 32; /* If there's no "/", assume
                           * that we want an EXACT IP
                           * address. Hence the 32 bit
                           * mask
                           **/
            } else {
                $bm = (int)substr($ip, ($spos+1));
                $ip = substr($ip, 0, $spos++);
            }
    
            $lip = ip2long($ip); /* Using this here to check IP validity
                                  * before storing it in the array...
                                  * We use ip2long() later for comparisons.
                                  *
                                  * You can store it this way - as a long - 
                                  * instead of as a string (I do) to
                                  * use less memory if you wish.
                                  *
                                  **/
    
            if ($bm === 0) continue; /* A bit mask of ZERO will block 
                                      * ALL IP addresses, skip it
                                      * for the example.
                                      **/
            if ($lip === false) continue; /* If it's an invalid IP, skip it,
                                           * you could optionally try to 
                                           * resolve it as a hostname using
                                           * gethostbyname() or gethostbynamel()
                                           * here...
                                           **/
            array_push($wl, (object)array('ip'=>$ip, 'mask'=>$bm));
        }
        return $wl;
    }
    
    
        $whitelist = make_whitelist(array("50.0.0.0/8", "202.16.0.0/16", "123.168.0.0/16", "1.1.1.1"));
    
        $ips_to_check = array("50.1.174.41", "42.123.100.23", "123.168.4.79", "1.1.1.2", "1.1.1.1");
    
        foreach ($ips_to_check as $ip) {
            foreach ($whitelist as $w) {
                if (ip_match($ip, $w->ip, $w->mask)) {
                    echo "\"$ip\" is allowed!\n";
                    continue 2;
                }
            }
            echo "\"$ip\" is NOT allowed!\n";
        }
    

    我知道这很多,但这里有很多可供人们思考、在搜索时查找,并希望能够用来让他们的生活更轻松!

    【讨论】:

      猜你喜欢
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 2013-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-08
      相关资源
      最近更新 更多