ホスト名のバリデーションについて考えてみた

ということで、ホスト名の入力バリデーションについて

を参考に考えてみました。

ホスト名の仕様

さて、ホスト名はどのような仕様でバリデーションしたらよいでしょう?

JPNICのページ(https://www.nic.ad.jp/ja/dom/system.html)では、以下のように説明されています。

ピリオド(.)で区切られた部分は「ラベル」と呼ばれます。1つのラベルの長さは63文字以下、ドメイン名全体の長さは、ピリオドを含めて253文字以下でなければなりません。ラベルには、英字(A~Z)、数字(0~9)、ハイフン( - )が使用できます(ラベルの先頭と末尾の文字をハイフンとするのは不可)。ラベル中では大文字・小文字の区別はなく、同じ文字とみなされます。

RFCを確認してみましょう。

最大長

DNS関連のRFCであるRFC1035(DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION)によると、

2.3.4. Size limits

Various objects and parameters in the DNS have size limits. They are listed below. Some could be easily changed, others are more fundamental.

labels 63 octets or less

names 255 octets or less

TTL positive values of a signed 32 bit number.

UDP messages 512 octets or less
http://tools.ietf.org/html/rfc1035#section-2.3.4

そして、

3.1. Name space definitions

...略...

To simplify implementations, the total length of a domain name (i.e., label octets and label length octets) is restricted to 255 octets or less.
http://tools.ietf.org/html/rfc1035#section-3.1

上記のように、ドメイン名は最大255オクテット、ラベルは最大63オクテットとされています。

ただし、これはDNSメッセージ中のドメイン名(内部表現)についてであり、ドメイン名として扱える文字数は、ラベルの数よりドットの数が必ず1つ少ないこととルートドメインの空ラベル分を除いて253文字以下となります(解説は、https://www.nic.ad.jp/ja/dom/system.htmlhttp://dnsops.jp/event/20130719/20130719-dns-primer-takizawa-4.pdfなどを参照してください)。

他のRFCも確認しておきましょう。

SMTP関連のRFCであるRFC5321(Simple Mail Transfer Protocol)では、

4.5.3.1.2. Domain

The maximum total length of a domain name or number is 255 octets.
http://tools.ietf.org/html/rfc5321#section-4.5.3.1.2

上記のようにドメイン名が最大255オクテットとされています。

それから、RFC1123(Requirements for Internet Hosts -- Application and Support)では、

Host software MUST handle host names of up to 63 characters and SHOULD handle host names of up to 255 characters.
https://tools.ietf.org/html/rfc1123#page-13

255文字までを扱うべきであるとなっており、上限は明示されていません

まとめると、最大長は、

  • RFC1035 … 253オクテット
  • RFC5321 … 255オクテット
  • RFC1123 … 無制限

となります。さて、どうしたらいいのでしょう?

長さを短くすると利便性が減るという意見も見られますが、そもそも、DNSで名前解決できないほど長いホスト名を許容する必要性はあるでしょうか?

少なくとも私は253文字を超えるホスト名が必要だったケースを知りませんし、そのような必要性を想像するのも難しいです。

ちなみに、以下の長いホスト名でもまだ216文字です。

please-try-to.send-me-an-email-if-you-can-possibly-begin-to-remember-this-coz.this-is-the-longest-email-address-known-to-man-but-to-be-honest.this-is-such-a-stupidly-long-sub-domain-it-could-go-on-forever.pacraig.com

ということで、通常は253文字までを許可すればアプリケーション仕様としても問題ないと思います。また、1つのラベルは63文字までとなります。

ホスト名にはIPアドレスも入力できる

それから、ホスト名にはIPアドレスも入力できるという仕様がRFC1123にあります。

詳細は、「EximのGHOST脆弱性の影響とバリデーションの関係」への疑問の「(3) そもそもホスト名にはIPアドレスも入力できる」を参照願います。

ホスト名のバリデーションの実装

ということで、ホスト名はFQDNの場合、253文字(バイト)まで受け付けることにします。

あとは、IPアドレスが入力された場合をどうするかです。これはアプリケーション仕様でしょうが、そんなに簡単ではないですね。たぶんこれをどうするか仕様書に書いてあることは少ないのではないかと思います。

とりあえず、より汎用的に

  • IPアドレスを受け取るかエラーとするかを選択可能にする
  • 入力が数字とドットのみの場合は、IPアドレスと判断する
    • 何故なら、そのようなドメイン名は現在存在しないから
  • IPアドレスを受け取る場合
    • 入力が数字とドットのみの場合、IPv4としての形式をチェックする
  • IPアドレスを受け取らない場合
    • 入力が数字とドットのみの場合、エラーとする

として、ホスト名をバリデーションする関数をPHPで書いてみました。なお、国際化ドメイン名およびIPv6アドレスについては考慮していません。

<?php

/**
 * Validate Hostname (FQDN)
 * 
 * @param string $hostname  FQDN or IPv4 address
 * @param bool   $accept_ip Wheather IPv4 address is accepted or not
 * @return boolean
 */
function validate_host($hostname, $accept_ip = false)
{
    if (! is_string($hostname)) {
        return false;
    }

    $len = strlen($hostname);

    // total lenght is not more than 253 bytes
    if ($len > 253) {
        return false;
    }

    $labels = explode('.', $hostname);

    // needs at least one dot
    if (count($labels) < 2) {
        return false;
    }

    $is_ipv4 = true;
    $alnum = implode('', range('0', '9')) . implode('', range('a', 'z'))
           . implode('', range('A', 'Z'));

    foreach($labels as $label) {
        $label_len = strlen($label);

        // label lenght is not more than 63
        if (! $label_len || $label_len > 63) {
            return false;
        }

        // the first byte and the last byte are not hyphens
        if (substr($label, 0, 1) === '-' || substr($label, -1, 1) === '-') {
            return false;
        }

        // allowed chars are alnum and hyphens
        if ($label_len !== strspn($label, $alnum . '-')) {
            return false;
        }

        // check if all chars are digits only
        if (! ctype_digit($label)) {
            $is_ipv4 = false;
        }
    }

    // in case all labels are made of digits only, that is IPv4 address.
    if ($is_ipv4) {
        if ($accept_ip) {
            // total lenght is not more than 15 bytes
            if ($len > 15) {
                return false;
            }

            // check IPv4 address format if you accept IPv4 address
            if (filter_var($hostname, FILTER_VALIDATE_IP) === false) {
                return false;
            }
        } else {
            // invalidate if you don't accecpt IPv4 address
            return false;
        }
    }

    return true;
}

仮にこのような実装のバリデーションを実施していれば、EximのGHOST脆弱性も防げたと思います。

ちなみに、上記のコードが長すぎると感じた方には、「Zend Framework 2のホスト名のバリデータ」を参照されることをお薦めします。

まとめ

まとめというより感想ですが。

  • 入力バリデーションは実際にどう実装するかが最大の問題
  • アプリケーション仕様ははっきりしない場合も多く、バリデーションが甘い場合も多いと思われる
  • どこまで厳格にバリデーションするか、どのように判断したらよいのか?
    • ホスト名の最大長は253文字でよいと思う

参考

Date: 2015/02/16

Tags: security, php