PHP フレームワークでのクライアント IP アドレス取得メソッドの実装について
いわゆる 4大フレームワーク(CakePHP, CodeIgniter, Symfony, Zend Framework)のクライアント IP アドレス取得メソッドについて最新のコードを調べてみました。
フレームワークに用意されているメソッドで取得する IP アドレスを偽装できるかどうかについてです。
ただし、CodeIgniter 以外には精通していませんので、解釈に誤りがあるかも知れません。
CakePHP
<?php /** * Gets remote client IP * * @return string Client IP address * @access public */ function getClientIP($safe = true) { if (!$safe && env('HTTP_X_FORWARDED_FOR') != null) { $ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR')); } else { if (env('HTTP_CLIENT_IP') != null) { $ipaddr = env('HTTP_CLIENT_IP'); } else { $ipaddr = env('REMOTE_ADDR'); } } if (env('HTTP_CLIENTADDRESS') != null) { $tmpipaddr = env('HTTP_CLIENTADDRESS'); if (!empty($tmpipaddr)) { $ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr); } } return trim($ipaddr); }
引数 $safe のデフォルト値が true ですので、デフォルトでは HTTP_CLIENT_IP があればそのアドレスが使われます。
また、HTTP_CLIENTADDRESS がある場合はそのアドレスが使われますが、HTTP_CLIENTADDRESS って何でしょうね?見たことありません。
CLIENT-IP ヘッダ、または、CLIENTADDRESS ヘッダを送ればほとんどの環境で IP アドレスを偽装できるのではないでしょうか。
CodeIgniter
<?php /** * Fetch the IP Address * * @access public * @return string */ function ip_address() { if ($this->ip_address !== FALSE) { return $this->ip_address; } if (config_item('proxy_ips') != '' && $this->server('HTTP_X_FORWARDED_FOR') && $this->server('REMOTE_ADDR')) { $proxies = preg_split('/[\s,]/', config_item('proxy_ips'), -1, PREG_SPLIT_NO_EMPTY); $proxies = is_array($proxies) ? $proxies : array($proxies); $this->ip_address = in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; } elseif (! $this->server('HTTP_CLIENT_IP') AND $this->server('REMOTE_ADDR')) { $this->ip_address = $_SERVER['REMOTE_ADDR']; } elseif ($this->server('REMOTE_ADDR') AND $this->server('HTTP_CLIENT_IP')) { $this->ip_address = $_SERVER['HTTP_CLIENT_IP']; } elseif ($this->server('HTTP_CLIENT_IP')) { $this->ip_address = $_SERVER['HTTP_CLIENT_IP']; } elseif ($this->server('HTTP_X_FORWARDED_FOR')) { $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; } if ($this->ip_address === FALSE) { $this->ip_address = '0.0.0.0'; return $this->ip_address; } if (strpos($this->ip_address, ',') !== FALSE) { $x = explode(',', $this->ip_address); $this->ip_address = trim(end($x)); } if ( ! $this->valid_ip($this->ip_address)) { $this->ip_address = '0.0.0.0'; } return $this->ip_address; }
これも、HTTP_CLIENT_IP を無条件で信用しています。HTTP_CLIENT_IP を偽造できる環境で IP アドレスの偽装が可能です。
何年も前からの既知の問題で、『CodeIgniter 徹底入門』にも注意するように記載されているのですが、知らないユーザもいたようです。
現在、本家に、デフォルトでは HTTP_CLIENT_IP を信用しないような修正を pull request しています。
Symfony2
<?php /** * Returns the client IP address. * * @param Boolean $proxy Whether the current request has been made behind a proxy or not * * @return string The client IP address * * @api */ public function getClientIp($proxy = false) { if ($proxy) { if ($this->server->has('HTTP_CLIENT_IP')) { return $this->server->get('HTTP_CLIENT_IP'); } elseif (self::$trustProxy && $this->server->has('HTTP_X_FORWARDED_FOR')) { return $this->server->get('HTTP_X_FORWARDED_FOR'); } } return $this->server->get('REMOTE_ADDR'); }
デフォルトでは REMOTE_ADDR しか使っていません。IP アドレスの偽装は困難ですね。コードもすっきりしており、すばらしいですね。
Zend Framework
<?php /** * Get the client's IP addres * * @param boolean $checkProxy * @return string */ public function getClientIp($checkProxy = true) { if ($checkProxy && $this->getServer('HTTP_CLIENT_IP') != null) { $ip = $this->getServer('HTTP_CLIENT_IP'); } else if ($checkProxy && $this->getServer('HTTP_X_FORWARDED_FOR') != null) { $ip = $this->getServer('HTTP_X_FORWARDED_FOR'); } else { $ip = $this->getServer('REMOTE_ADDR'); } return $ip; }
デフォルトでは、HTTP_CLIENT_IP があればそのアドレス、次に、HTTP_X_FORWARDED_FOR があればそのアドレスを使います。
CLIENT-IP ヘッダ、または、X-FORWARDED-FOR ヘッダを送ればほとんどの環境で IP アドレスを偽装できるのではないでしょうか。
まとめ
Symfony2 以外はデフォルトが安全にはなっていません。これらのメソッドを使い、IP アドレスによるアクセス制限を実装している場合は、簡単に IP アドレス偽装で制限を突破される可能性があります。
実際に、偽装可能かどうか確認する方法を IP アドレスが偽装可能か確認してみよう - A Day in Serenity @ kenjis に記載しました。