概要
PHPでは、gethostbyaddr関数を利用することでDNS逆引きを行い、IPアドレスからホスト名を取得できます。
しかし、ここから返ってきたホスト名は信頼できるものではありません。
そもそもDNS逆引きの結果が信頼できるものではなく、このホスト名が正しいものかDNS正引きを行って検証する必要があります。
この投稿では、このDNS正引きを行い、ホスト名が信頼できるものなのか検証する実装を紹介します。
処理
PHPには、ホスト名からDNS正引きを行い、IPアドレスの一覧を取得するgethostbynamel関数が存在します。
この関数はIPアドレスの配列を返します。そのため、in_array関数で結果の配列に元のIPアドレスが含まれているか検証し、含まれていればそのホスト名が信頼できるものであると判断できます。
ただし、gethostbynamel関数はIPv4のIPアドレスを返してきます。
IPv4 アドレスの配列を返します。 もし
hostname
が解決できなかった場合はFALSE
を返します。
このため、IPv6のIPアドレスではgethostbynamel関数を利用することはできません。
そこで、代わりにdns_get_record関数を利用します。
第二引数に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 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 {
/**
* 信頼できない場合
*/
// ...
}
-
IPv4のアドレスを持つ私のドメインで確認しました。 ↩