LoginSignup
17
19

More than 5 years have passed since last update.

IPアドレスから取得したホスト名が信頼できるか、DNS正引きを行って検証する

Last updated at Posted at 2015-09-11

概要

PHPでは、gethostbyaddr関数を利用することでDNS逆引きを行い、IPアドレスからホスト名を取得できます。

PHP: gethostbyaddr - Manual

しかし、ここから返ってきたホスト名は信頼できるものではありません。
そもそもDNS逆引きの結果が信頼できるものではなく、このホスト名が正しいものかDNS正引きを行って検証する必要があります。

逆引き - Wikipedia

  • 逆引きを設定していないIPアドレスも存在する。
  • 逆引きで返ってくる結果は所詮は自称にすぎないので、信頼性をそれなりにでも確保するためには、得られた結果をもう一度正引きして、元のIPアドレスと合致するか確かめる必要があるだろう。
    • つまりドメイン名を正引きして得たIPアドレスに対して、逆引きして得たドメイン名は一致しないこともある。
  • ISP等によっては、クライアントホストのIPアドレスの逆引きに、アクセスポイント名が含まれるドメイン名を設定しているところもある。

この投稿では、このDNS正引きを行い、ホスト名が信頼できるものなのか検証する実装を紹介します。

処理

PHPには、ホスト名からDNS正引きを行い、IPアドレスの一覧を取得するgethostbynamel関数が存在します。

PHP: gethostbynamel - Manual

この関数はIPアドレスの配列を返します。そのため、in_array関数で結果の配列に元のIPアドレスが含まれているか検証し、含まれていればそのホスト名が信頼できるものであると判断できます。

PHP: in_array - Manual

ただし、gethostbynamel関数はIPv4のIPアドレスを返してきます。

PHP: gethostbynamel - Manual

IPv4 アドレスの配列を返します。 もし hostname が解決できなかった場合は FALSE を返します。

このため、IPv6のIPアドレスではgethostbynamel関数を利用することはできません。
そこで、代わりにdns_get_record関数を利用します。

PHP: dns_get_record - Manual

第二引数にDNS_AAAA定数を指定することで、DNSレコードのうち、IPv6のIPアドレスを持つAAAAレコードのみを取得します。
CNAMEレコードによって定義された別名(エイリアス)は解決されるようです1

コード

以上をふまえたコードが以下になります。

/**
 * @param string $hostname ホスト名
 * @param string $ip_address ホスト名に対応する(はずの)IPアドレス
 * @return bool ホスト名が信頼できる場合はtrue、そうでない場合はfalseを返します
 */
function is_reliable_host ($hostname, $ip_address)
{
    $ip_list = false;

    if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        /**
         * IPv6
         */

        $records = @dns_get_record($hostname, DNS_AAAA);
        if ($records) {
            $ip_list = array_column($records, 'ipv6');
        }
    } elseif (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        /**
         * IPv4
         */

        $ip_list = gethostbynamel($hostname);
    }

    return ($ip_list && in_array($ip_address, $ip_list, true));
}

なお、上記のコードではPHP 5.5から追加されたarray_column関数を利用しています。

PHP: array_column - Manual

このため、PHP 5.5未満には対応していません。
PHP 5.5未満に対応する場合、代わりに以下のコードを利用して下さい。

PHP 5.5未満対応
/**
 * @param string $hostname ホスト名
 * @param string $ip_address ホスト名に対応する(はずの)IPアドレス
 * @return bool ホスト名が信頼できる場合はtrue、そうでない場合はfalseを返します
 */
function is_reliable_host ($hostname, $ip_address)
{
    $ip_list = false;

    if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        /**
         * IPv6
         */

        $records = @dns_get_record($hostname, DNS_AAAA);
        if ($records) {
            $ip_list = array_map(function ($v) { return $v['ipv6']; }, $records);
        }
    } elseif (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        /**
         * IPv4
         */

        $ip_list = gethostbynamel($hostname);
    }

    return ($ip_list && in_array($ip_address, $ip_list, true));
}

使い方

以下のように利用します。

/**
 * アクセスしてきたユーザのIPアドレス
 */
$REMOTE_ADDR = $_SERVER['REMOTE_ADDR'];

/**
 * DNS逆引きにより、IPアドレスからホスト名を取得
 */
$HOST_NAME = gethostbyaddr($REMOTE_ADDR);

/**
 * 取得したホスト名が信頼できるか検証
 */
if (is_reliable_host($HOST_NAME, $REMOTE_ADDR)) {
    /**
     * 信頼できる場合
     */

    // ...
} else {
    /**
     * 信頼できない場合
     */

    // ...
}

  1. IPv4のアドレスを持つ私のドメインで確認しました。 

17
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19