提醒:本页面将不再更新、维护或者支持,文章、评论所叙述内容存在时效性,涉及技术细节或者软件使用方面不保证能够完全有效可操作,请谨慎参考!

不久前看到WAVDB上一篇 《BlueCMS信息门户系统存在getip()注射漏洞》 ,让我感觉到WEB编程中安全隐患处处都会存在,往往很多情况下都是我们大意,导致问题的产生,经典的安全问题就是SQL注入攻击(SQL Injection Attack)以及XSS跨站脚本攻击,这些安全问题说到底就是对用户数据校验不严格所造成的,经典的安全编程模式应该是脱离程序员假定的数据格式,要能够接受来自客户任何数据,然后对这些数据进行甄别,过滤掉不符合原假定格式的数据,因为这些数据可能是无用的或者是有害的,说到底,大部分安全问题还是由于对来自用户数据的不严格判断过滤所导致的。

下面再来看看BlueCMS的getip()安全漏洞,原有的代码摘录如下:

<?php
function getip() {  
  if (getenv('HTTP_CLIENT_IP'))  
  {  
    $ip = getenv('HTTP_CLIENT_IP'); //可伪造  
  }  
  elseif (getenv('HTTP_X_FORWARDED_FOR'))   
  {   
    $ip = getenv('HTTP_X_FORWARDED_FOR'); //可伪造  
  }  
  elseif (getenv('HTTP_X_FORWARDED'))   
  {   
    $ip = getenv('HTTP_X_FORWARDED');  
  }  
  elseif (getenv('HTTP_FORWARDED_FOR'))  
  {  
    $ip = getenv('HTTP_FORWARDED_FOR');   
  }  
  elseif (getenv('HTTP_FORWARDED'))  
  {  
    $ip = getenv('HTTP_FORWARDED');  
  }  
  else  
  {   
    $ip = $_SERVER['REMOTE_ADDR'];  
  }  
  return $ip;  
}

通过上述代码可以看出HTTP_CLIENT_IP和HTTP_X_FORWARDED_FOR是可以伪造的,我们可以在header中插入X-Forwarded-For的请求头,里面构造一段SQL注射攻击语句,如果说程序员不加以防范直接将getip()组合到SQL语句,然后进行数据库查询的话,那么这次攻击就得逞了。

避免的方法也很简单,对用户的IP地址进行有效性判断。如下面的代码:

function ipv4_valid($ip) {
  if (!empty($ip) && ip2long($ip)!=-1) {
    $reserved_ips = array (
    array('0.0.0.0','2.255.255.255'),
    array('10.0.0.0','10.255.255.255'),
    array('127.0.0.0','127.255.255.255'),
    array('169.254.0.0','169.254.255.255'),
    array('172.16.0.0','172.31.255.255'),
    array('192.0.2.0','192.0.2.255'),
    array('192.168.0.0','192.168.255.255'),
    array('255.255.255.0','255.255.255.255')
    );

    foreach ($reserved_ips as $r) {
      $min = ip2long($r[0]);
      $max = ip2long($r[1]);
      if ((ip2long($ip) >= $min) &&
	(ip2long($ip) <= $max)) return false;
    }
    return true;
  } else {
    return false;
  }
}

上面的代码除了判断了IP(v4)的有效性外还排除了私有保留地址,当然IP判断也可以更容易一些,那就是用PHP原生的 filter_var 函数(PHP 5.2及以上版本)进行有效性检查,具体的用法可以参考PHP手册,下面只列举判断IP的用法:

function ipv4_valid($ip) {
  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
function ipv6_valid($ip) {
  return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}

上面分别给出了鉴别IPv4和IPv6有效性的函数,如何排除私有保留IP地址?其实我们可以利用FILTER_FLAG_NO_PRIV_RANGE和FILTER_FLAG_NO_RES_RANGE标志位,所以兼容IPv4和IPv6并排除私有和保留地址的IP检查可以这样写:

function ip_valid($ip) {
  if (filter_var($ip, FILTER_VALIDATE_IP, 
      FILTER_FLAG_IPV4 | 
      FILTER_FLAG_IPV6 |
      FILTER_FLAG_NO_RES_RANGE |
      FILTER_FLAG_NO_PRIV_RANGE) === false)
    return false;
  return true;
 }

再根据刚才的getip()函数,配合这段校验IP地址的ip_valid()函数,就可以杜绝此类攻击了。