【実務・中級編】 VPN接続時のDNSリーク発生原因と防止策 – サイバーセキュリティとプライバシー保護実践ガイド

はじめに:VPNを過信しているエンジニアが踏む「DNSリーク」という地雷

Web APIの設計やインフラの構築・運用に日夜奔走するエンジニアの皆さん、お疲れ様です。カフェのフリーWi-Fiや出先のホテルから、会社の検証環境やクラウド上のプライベートサブネットにアクセスする際、とりあえず個人向け(あるいは業務用の)VPNクライアントを「ポチッ」と有効にして安心していませんか?

「よし、これで俺の通信は完全に暗号化され、IPアドレスも隠蔽された!」

……ちょっと待ってください。その自信、本当にパケットキャプチャのログで裏付けを取ったものでしょうか?
実務の現場において、「VPNに接続しているにもかかわらず、名前解決のクエリだけがISP(プロバイダ)のDNSサーバーへ素通りしている」という致命的な事象、いわゆるDNSリークは、驚くほど高頻度で発生しています。

今回は、パケットがOSのネットワークスタックを駆け巡るリアルな挙動を追いながら、なぜDNSリークが起きるのか、そのメカニズムをRFCやOSの仕様に立ち返って解き明かします。そして、インフラエンジニアとして絶対に押さえておきたい具体的な防止策とデバッグ手法を、泥臭い実務の知見を交えて叩き込みます。

—

1. なぜDNSリークが起きるのか?通信フローとOSの挙動

まずは、VPN接続下で何が起きているのか、名前解決(DNS)のパケットの行方を追ってみましょう。

通常、VPNクライアントを有効にすると、仮想ネットワークインターフェース(TUN/TAPデバイスなど)が生成され、OSのルーティングテーブルが書き換わります。理想的には、すべてのトラフィック(UDP/TCP 53番ポートへのDNSクエリを含む)が、VPNサーバー側のDNSフォワーダーへトンネリングされるべきです。

しかし、現代のモダンなオペレーティングシステム(Windows 10/11、macOS、Linuxの各種NetworkManagerなど)は、マルチホーム環境(複数のネットワークインターフェースが同時に存在すること)をスマートに(あるいは余計なお世話として)処理しようとします。

スプリットトンネリングとDNSの優先順位(Mullti-Homed Name Resolution)

多くのOSでは、複数のインターフェースからDNSサーバーが指定された場合、RFC 1123や各OS独自のアルゴリズムに基づいて「どのDNSサーバーに問い合わせるか」を動的に決定します。
特に、Wi-FiルーターからDHCPで配布されたローカルのDNS(例: 192.168.1.1)や、ISPのキャリア網のDNSサーバーのメトリック(優先度)が、VPN側の仮想インターフェースのそれよりも高く評価されてしまうと悲劇が起こります。

[クライアントPC]
  │
  ├─ 1. ブラウザで "api.internal.example.com" を引こうとする
  │
  ├─ 2. OSのDNSリゾルバが稼働
  │     (ここでメトリック設定やOSのバグにより、誤ったインターフェースを選択)
  │
  ├─ 3. 【リーク発生】ISPのDNSサーバーへUDP/53で平文のクエリが飛ぶ!
  │     (「おっ、このユーザーは今 `api.internal.example.com` を探してるな」とISPに丸見え)
  │
  └─ 4. VPNトンネル経由 (本来のルート)
        ※実際の暗号化通信データ自体はVPNを通るが、宛先ドメインの足跡がISPやパブリックWi-Fiの盗聴者に完全暴露される

この状態の何が恐ろしいかといえば、「通信のペイロード(中身)自体はAES等で暗号化されてVPNトンネルを流れているにもかかわらず、アクセスしようとしたWebサイトのドメイン名が、道中のDNSルックアップによって丸裸にされている」という点です。プライバシー保護の観点からも、セキュリティ監査の観点からも、これは完全なコンプライアンス違反や情報漏洩につながる脆弱性です。

—

2. 実務で使えるDNSリークの検測・デバッグ手法

「俺の環境は大丈夫か?」と思ったら、感覚で判断せず、コードとコマンドで叩いて確認するのがエンジニアの流儀です。手元の端末から、実際にDNSがどこに飛んでいるかを暴く手法を見ていきましょう。

手法A: コマンドラインによる名前解決の追跡(dig / nslookup)

まずは、特定のパブリックDNSチェッカーや、意図的にコントロールされたドメインに対して、どのサーバーが応答しているかを dig コマンドで確認します。

# 自身のグローバルIPや名前解決の経路を確認する定番のテスト
dig +short o-o.myaddr.l.google.com @txt.dns.google

# または、現在どのDNSサーバーが参照されているかを詳細に確認
scutil --dns (macOSの場合)
systemd-resolve --status (Linux systemd-resolved環境の場合)
ipconfig /all (Windowsの場合)

もし、scutil --dns などの出力結果にある nameserver [0] や nameserver [1] に、VPNプロバイダーが指定したものではなく、自宅のルーターIPやISPのDNS(例: 192.168.x.x やキャリアの固定IP)が鎮座していた場合、あなたのOSは盛大にDNSをリークしています。

手法B: PythonスクリプトによるAPI経由の自動検証

インフラのCI/CDパイプラインや、開発端末のキッティングスクリプトの一部として、自動的にDNSリークを検知する簡易的なPythonスクリプトの例を以下に示します。ここでは、ランダムなサブドメインを用いたDNSルックアップの挙動を確認するコンセプトを実装しています。

import socket
import urllib.request
import json

def check_external_ip():
    """現在のグローバルIPアドレスを外部API経由で取得する"""
    try:
        url = "https://api.ipify.org?format=json"
        req = urllib.request.urlopen(url, timeout=5)
        data = json.loads(req.read().decode('utf-8'))
        return data.get("ip")
    except Exception as e:
        return f"IP取得失敗: {e}"

def resolve_target_domain(domain):
    """指定されたドメインの名前解決を行い、紐づくIPアドレスリストを返す"""
    try:
        # getaddrinfoを使用してIPv4/IPv6のアドレスを取得
        result = socket.getaddrinfo(domain, None)
        ip_addresses = set([item[4][0] for item in result])
        return list(ip_addresses)
    except socket.gaierror as e:
        return f"名前解決エラー: {e}"

if __name__ == "__main__":
    print("=== DNSリーク・接続確認診断ツール ===")
    current_ip = check_external_ip()
    print(f"[+] 現在認識されているグローバルIP: {current_ip}")
    
    target = "dnsleaktest.com" # テスト用ドメイン(環境に合わせて変更してください)
    resolved_ips = resolve_target_domain(target)
    print(f"[+] '{target}' の名前解決結果: {resolved_ips}")
    print("※ 注意: このIPがVPNサーバーのものでなく、ISPのものであればDNSリークが発生しています。")

—

3. 根本的な防止策:OS設定とVPNクライアントのチューニング

DNSリークの原因がわかったところで、これを確実に塞ぐための実務的なアプローチを解説します。単に「VPNアプリのスイッチを入れる」だけでなく、OSのネットワークスタックの挙動をねじ伏せる設定が必要です。

1. VPNクライアント側の「Network Lock(Kill Switch)」機能の有効化

プロ仕様のVPNクライアント(OpenVPNやWireGuardをベースにしたモダンなクライアント)には、「Kill Switch」や「Network Lock」と呼ばれる機能が備わっています。
これは、万が一VPNトンネルが意図せず切断されたり、ルーティングテーブルの書き換えが競合したりした際、ローカルの物理インターフェースからのトラフィック(特にUDP/53)をファイアウォール(iptablesやWindows Firewall)レベルで強制遮断する仕組みです。必ず有効化しておきましょう。

2. Linux環境(systemd-resolved / NetworkManager)での強制DNSルーティング

Linuxサーバーや開発用デスクトップでOpenVPNやWireGuardを運用する場合、systemd-resolved が余計なキャッシュやフォワードを行い、DNSリークを引き起こすことがよくあります。
/etc/systemd/resolved.conf を以下のように適切に設定し、DNSのグローバルフォワーダーをVPN専用に固定します。

[Resolve]
# VPN接続時に指定されたDNSサーバーを優先し、フォールバックを無効化する
DNS=10.8.0.1
FallbackDNS=
Domains=~.
DNSOverTLS=no
Cache=no

設定を反映させるためには、デーモンを再起動します。

# systemd-resolvedの設定をリロードして適用
sudo systemctl restart systemd-resolved

# 現在のDNSルーティング状態を厳密に確認
resolvectl status

3. Windows環境での「LLMNR」および「NetBIOS」の無効化

Windows OSは、通常のDNSで名前解決に失敗した際、ローカルネットワークに向けてLLMNR(Link-Local Multicast Name Resolution)やNetBIOS over TCP/IPを使ったブロードキャスト名前解決を勝手に行います。これがVPN網の外側に漏れ出す原因になります。
インフラ管理者は、GPO(グループポリシー)やレジストリ調整を通じて、パブリックネットワーク接続時にはこれらの機能を強制的に無効化するプロファイル設計を行うべきです。

—

4. おわりに:ゼロトラスト時代におけるネットワーク運用の心構え

VPN接続時のDNSリークは、目に見えないところでプライバシーを切り売りする、非常に厄介な「見えないバグ」のようなものです。
「暗号化しているから大丈夫」という思い込みを捨て、パケットの挙動を疑い、CLIツールやスクリプトを駆使して自らの手でトラフィックの整合性を検証する――これこそが、信頼性の高いインフラを支えるエンジニアの基本姿勢です。

皆さんも、今日の業務が終わったら、手元の端末で scutil --dns や resolvectl status を叩いてみてください。もしかすると、あなたのブラウジング履歴が、今この瞬間も最寄りのISPに丸見えになっているかもしれませんよ?

それでは、セキュアで快適なネットワークライフを!

コメント

タイトルとURLをコピーしました