2018年8月

前阵子受到羊毛党的报复,对我们短信接口发起攻击。
分析LOG发现有几千个IP,我就不相信他有这么多肉鸡……怀疑我们获取到的IP是伪造不真实的IP。
IP有这么好伪造吗?那就视乎你怎么获取IP了,看THINKPHP获取IP的代码,主要是通过下面的环境变量获取:
REMOTE_ADDR
HTTP_X_FORWARDED_FOR
HTTP_CLIENT_IP

/**
 * 获取客户端IP地址
 * @param integer   $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
 * @param boolean   $adv 是否进行高级模式获取(有可能被伪装)
 * @return mixed
 */
public function ip($type = 0, $adv = false)
{
    $type = $type ? 1 : 0;
    static $ip = null;
    if (null !== $ip) {
        return $ip[$type];
    }

    if ($adv) {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $pos = array_search('unknown', $arr);
            if (false !== $pos) {
                unset($arr[$pos]);
            }
            $ip = trim(current($arr));
        } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (isset($_SERVER['REMOTE_ADDR'])) {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
    } elseif (isset($_SERVER['REMOTE_ADDR'])) {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    // IP地址合法验证
    $long = sprintf("%u", ip2long($ip));
    $ip   = $long ? array($ip, $long) : array('0.0.0.0', 0);
    return $ip[$type];
}

我们代码里面都是通过 $this->request->ip() 获取IP的……可能我们被羊毛骗了

再看看下面这个代码
HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP
伪造就是这么容易,这就是XFF欺骗

$url = 'http://www.fake.com/index.php';
// 参数
$data_string = '';
$URL_Info = parse_url($url);
$request = '';
if (!isset($URL_Info["port"])) {
    $URL_Info["port"] = 80;
}
$request.="POST ".$URL_Info["path"]." HTTP/1.1\n";
$request.="Host: ".$URL_Info["host"]."\n";
$request.="Referer: ".$URL_Info["host"]."\n";
$request.="Content-type: application/x-www-form-urlencoded\n";
// HTTP_X_FORWARDED_FOR 的值,可以随心所欲
$request.="X-Forwarded-For:192.168.1.4\n";
// HTTP_CLIENT_IP 的值,可以随心所欲
$request.="client_ip:192.168.1.5\n";
$request.="Content-length: ".strlen($data_string)."\n";
$request.="Connection: close\n";
$request.="\n";
$request.=$data_string."\n";
$fp = fsockopen($URL_Info["host"], $URL_Info["port"]);

fputs($fp, $request);
fclose($fp);

了解到通过REMOTE_ADDR获取的IP是不能伪造,不过我们生产环境用了反向代理,架构类似下图

d83868e7b403360a546824d0a1b808f2.png

反向代理每个虚拟主机配置都加了这样的配置

proxy_set_header Host $host;
proxy_set_header X-Real-Ip  $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

这样是可以转发 HTTP_X_FORWARDED_FOR 到RS服务器,不过 REMOTE_ADDR 还是代理的IP,不知道

proxy_set_header X-Real-Ip  $remote_addr;

这个作用是什么,看着不是把 REMOTE_ADDR 转发到RS服务器,当时为了快速解决我把

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 

改成

proxy_set_header X-Forwarded-For $remote_addr;

然后这样获取
$this->request->ip(0, true)

set_real_ip_from 10.10.10.10;
real_ip_header X-Forwarded-For;

PHP中$_SERVER没有走代理访问的时候是没有下面两个值的

[HTTP_X_FORWARDED_FOR] => 27.46.8.255
[HTTP_X_REAL_IP] => 27.46.8.255
[HTTP_X_FORWARDED_FOR] => 192.168.1.4, 27.46.8.255
[HTTP_X_REAL_IP] => 27.46.8.255

未完待续....