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 に記載しました。