DNSの脆弱性の歴史とクラウド

By

Category: Unit 42

Tags:

A conceptual image illustrating the concept of DNS vulnerabilities through a set of folders with one opened by an attacker.

This post is also available in: English (英語)

概要

時折、世界中の何十億台ものデバイスを危険にさらす、新たなDomain Name System (ドメイン ネーム システム - DNS)の脆弱性が発見されています。一般に、DNSの脆弱性は重大です。自分の銀行口座のWebサイトを閲覧したときに、DNSリゾルバが銀行のWebサイトのIPアドレスを返す代わりに、攻撃者のWebサイトのアドレスを返すことを想像してみてください。攻撃者のWebサイトは、銀行のWebサイトとそっくりです。それだけではなく、URLバーにも間違いは見当たりません。ブラウザが、銀行のWebサイトであると本当に認識しているためです。これが、DNSキャッシュポイズニングの一例です。

このブログでは、DNSについて説明するほか、過去の脆弱性から最近発見された高度なものまで、DNSキャッシュポイズニングの脆弱性の歴史を振り返ります。

本ブログで取り上げる脆弱性は、過去20年間に行われたほぼすべてのDNSキャッシュポイズニング攻撃に利用されてきた脆弱性です。

パロアルトネットワークスのお客様は、さまざまな方法(次世代ファイアウォールDNSセキュリティサービス サブスクリプションおよびPrisma Cloud)によって、本ブログで概説する攻撃から保護されています。

DNSとは何か?

Domain Name System (ドメイン ネーム システム - DNS)は、インターネット(またはプライベートネットワーク)に接続されたコンピュータ、サービス、その他のリソースの階層構造の分散型ネームシステムです。さまざまな情報を、参加している各エンティティに割り当てられているドメイン名に関連付けます。

簡単に言えば、DNSとは、主として名前をIPアドレスに変換するために使用されるプロトコルです。http://www.example.comを閲覧するときに、www.example.comのドメイン名は93.184.216.34などの実際のIPアドレスに変換されます。

DNSサーバーとは何か?

DNSサーバーはDNS解決を提供するデバイスまたはプログラムで、通常ネームサーバーと呼ばれます。ほとんどのデスクトップおよびモバイルのオペレーティングシステムに組み込まれているDNSクライアントが、DNSサーバーとやり取りして名前をIPアドレスに変換します。

結局のところ、権威ネームサーバーと再帰ネームサーバーという2つのタイプのサーバーしか存在しないことを覚えておいてください。権威ネームサーバーはドメインを扱うネームサーバーで、チェーンの最後に位置しています。権威ネームサーバー以外のものは、すべて再帰ネームサーバーです。たとえば、ルーターのDNSサーバー、ISPのDNSサーバー、KubernetesクラスタのDNSサーバーはすべて再帰ネームサーバーです。再帰サーバーはすべて、同じように動作します。何回かの再帰の後、最後に、目的のドメインの権威ネームサーバーに問い合わせます(権威ネームサーバーと再帰ネームサーバー両方の機能を実行するサーバーもありますが、本ブログでは取り上げません)。

仕組み

このDNSプロトコルの図は、要求がデバイスからルーターのリゾルバ、そしてルートサーバー、TLDサーバー、要求されたWebサイトの権威サーバーへと流れる模様を示しています。
図1: DNSプロトコルの仕組み


DNSリゾルバはDNS階層の(たとえばKubernetesクラスタの)最下層ノードです。DNSの仕組みの基本概念は次のようなものです。DNSリゾルバは自分が知らないドメインについて尋ねられると、それをDNSルートサーバーに尋ねます。ルートサーバーは階層の最上位ノードで、そのアドレスはリゾルバにハードコードされています。ルートサーバーは該当するトップレベルドメイン(TLD)のDNSサーバーのアドレスリストを返します。このDNSサーバーは、該当するDNSゾーン(.com、.netなど)を受け持ちます。TLDサーバーは階層内の次の下位サーバーのアドレスを返し、権威サーバーに到達するまで、これが行われます。

クラウド内のDNS

近年、クラウドコンピューティングの人気が急増しています。クラウド製品は、直感的でない方法でDNSが組み込まれていることがあります。クラウド内の仮想マシンの場合は、かなり直観的です。DNSリゾルバは、仮想マシンのオペレーティングシステムによって決まります。Windowsを使用している場合は、WindowsにDNSリゾルバが組み込まれています。Linuxを使用している場合は、ディストリビューションに依存します。

しかし、Kubernetesのような他のクラウド製品の場合はどうでしょうか? 単純な仮想マシンとは異なり、KubernetesはDNSリゾルバが組み込まれていないカスタム仮想マシンをノードに使用しています。

さらに、各Kubernetesクラスタには、特別にコンテナ化されたDNSリゾルバが含まれています。クラスタ内のすべてのアプリケーションはこのコンテナ化されたDNSリゾルバにDNS要求を転送し、DNSリゾルバは要求を処理します。

Kubernetesのバージョン1.10までは、DNS要求を処理、キャッシュ、転送するためのアプリケーションパッケージkube-dnsがありました。kube-dnsのコアアプリケーションはdnsmasqでした。dnsmasqは、DNSサービスに小規模ネットワークを提供する、構成しやすい軽量のDNSフォワーダです。その後、KubernetesはCoreDNSに移行しました。これは、Goで記述されたオープンソースのDNSサーバーです。CoreDNSは、柔軟性のある高速なDNSサーバーです。

キャッシュ

キャッシュは、データを格納し、そのデータに対する以後の要求を迅速に処理するための、ハードウェアまたはソフトウェアコンポーネントです。キャッシュは、DNSサーバーの非常に重要な機能です。DNSサーバーは、以前に変換した名前をキャッシュに格納します。

たとえば、クライアントCがwww.example.comにアクセスしようとして、ネームサーバーがwww.example.com93.184.216.34に解決した場合、www.example.comのこのIPアドレスが任意の期間保存され、次のDNSクライアントがこの名前を解決しようとしてキャッシュに問い合わせると、キャッシュから迅速に回答が返されます。

DNSキャッシュポイズニングとは何か?

DNSキャッシュポイズニングはDNSサーバー上で行われる攻撃タイプで、最終的にDNSサーバーが、攻撃者が制御するIPアドレスを非攻撃者が制御するドメインに保存することで終了します。

たとえば、攻撃者がDNSサーバーをまんまとだまして、www.example.comのIPアドレスを13.37.13.37として保存させます。これは、実際のIPアドレスではなく、攻撃者が制御する不正なIPアドレスです。

これ以降、キャッシュされたIPアドレスがタイムアウトするまで、www.example.comを解決しようとするDNSクライアントはすべて、攻撃者のWebサイトに「リダイレクト」されます。

過去のDNSの脆弱性

緩和策なし

過去20年間、多くのDNS攻撃が発見されました。ここでは、最もよく知られている攻撃であるDNSキャッシュポイズニングについて説明します。この攻撃は、DNSスプーフィングとも呼ばれています。

DNSキャッシュポイズニングの仕組み

上で述べたように、リゾルバがドメインのIPアドレスを解決するには、多少時間がかかります。通常、権威サーバーに到達するまでに、複数のサーバーに問い合わせる必要があります。攻撃者はこの時間を悪用して、偽の回答をリゾルバに送信します。

この攻撃は、次のシナリオで行うことができ、すべてのシナリオにおいて攻撃者はリゾルバにパケットを送信できるようになります。

  • リゾルバが、リスニングポートをインターネットに対して開いたままにします。
  • 攻撃者が、内部ネットワークでエンドポイントをなんとかうまく制御します。
  • 内部ネットワーク内のクライアントが、攻撃者が制御するWebサイトを閲覧します。

たとえば、攻撃者が内部ネットワーク内の1つのエンドポイントに侵入し、ローカルリゾルバのキャッシュを汚染しようとしているとします。重要なのは、この攻撃を、ISPのリゾルバなどより重要なリゾルバに、まったく同じ方法で実施できるという点です。

クライアントはwww.example.comにアクセスし、ドメインがリゾルバのキャッシュから消えているため、上で説明したプロセスを開始して、ドメインのアドレスを解決します。その間、攻撃者はDNSサーバーの回答に見せかけて、偽のIPアドレスを含むアンサーパケットをリゾルバに送信します。これが攻撃の基本概念ですが、いくつか緩和策があります。トランザクションIDやソース ポート ランダマイゼーションなどです。

DNSはトランザクションID (TXID)と呼ばれるものを使用して、各応答を正しい要求と一致させています。攻撃者は正しいトランザクションIDを持つ応答を提供する必要があり、さもないとDNSサーバーはパケットを破棄します。何年も前に開発された時点では、このメカニズムはセキュリティ機能ではなく、ただ単に応答を要求と一致させる方法でした。DNSの脆弱性が発見される前、DNSは昇順のインデックスをトランザクションIDとして使用していました。攻撃者は、その数値を簡単に推測することができました。

DNSの脆弱性があると、DNSプロトコルはDNSキャッシュポイズニング攻撃(ここで説明した攻撃など)を受けやすくなります。ここでは、攻撃者はルーターのリゾルバに干渉して、要求されたドメインの解決に使用するための、偽のIPアドレスを提供します。
図2: DNSキャッシュポイズニング攻撃

攻撃者はドメインを購入し、自身が制御するDNSサーバーを構成する必要がありました。次に、攻撃者はローカルネットワーク内のリゾルバに自身のドメインについて尋ねます。ローカルリゾルバは、攻撃者が制御するDNSサーバーを知っているDNSサーバーに到達するまで、要求を階層型DNSの上位に転送します。この時点で、元のDNS要求(攻撃者が内部ネットワークから発行した)は攻撃者が制御するDNSサーバーに到達しているため、トランザクションIDが攻撃者に暴露されます。

攻撃者は先ほど発見したトランザクションIDを使用して、偽の応答が受け入れられるために必要なトランザクションIDを得ることになります。この場合、攻撃者は複数のトランザクションIDで複数の偽の応答を送信して、いずれかが正しいことを確認できます。攻撃者の残りの作業は、ローカルのDNSリゾルバのキャッシュにないドメインに対するDNS要求を発行し、そのドメインのトランザクションIDを使用して直ちに偽の応答を送信するだけです。

ローカルリゾルバは正しいトランザクションIDの応答を取得し、他は破棄して、アドレスをキャッシュします。このあと任意の期間、そのドメインにアクセスしたローカルクライアントは攻撃者のWebサイトに転送されます。

新しい緩和策

実施された明確な緩和策は、各要求のトランザクションIDをランダム化することでした(昇順のカウンタを使用するのではなく)。この緩和策により、攻撃の実施が非常に困難になりました。どれくらい困難になったか? MAX_SIZE_OF_QUERY_IDが困難になりました。トランザクションIDは16ビットであるため、この緩和策により、攻撃は以前の65,536倍困難になりました。悪くはありませんが、万全でもありません。

2回戦に突入!

こうした緩和策は、攻撃者をしばらくの間、遠退けることができましたが、最終的にインターネットは追いつきました。16ビットのランダム化キーはかなり大きいですが、十分に大きいわけではありません。計算してみましょう。

次の計算では、いくつかのデータを指定する必要があります。

  • 標準的なDNS応答のサイズ: 100バイト= 800ビット
  • 攻撃者の帯域幅: 1メガビット(Mb)/秒= 1,000,000ビット
  • 正当な応答がリゾルバに戻るのにかかる時間: 2秒

(2秒に関する注記: 現代のインターネット接続では、キャッシュされていないドメインであっても、間違いなく2秒未満でDNS要求を実行できます。しかし、この例では待ち時間を設定しました。また、インターネット速度も過去のもの(1 Mb/秒)を選択しました。

使用できるトランザクションIDは65,536あり、攻撃を成功させるのに必要なトランザクションIDを持つ応答を送信するために、2秒の時間があります。攻撃ごとの成功率を計算するには、リゾルバに実際の正当な応答が到達する前に、攻撃者が発信できる応答パケットの数を見つけ出す必要があります。これは簡単に計算できます。帯域幅をパケットのサイズで割ったものに時間を掛けます。

帯域幅が1 Mb/秒の場合、攻撃者は1秒に最大1,000,000/800 = 1,250の応答パケットを送信できます。攻撃者が持つ時間は2秒であるため、2,500の応答パケットを送信できます。帯域幅が1 Mb/秒で成功率が50%以上の場合、見込みのある攻撃を実施するのに必要な試行回数は約18回です。

(計算の詳細は、ここをご覧ください。1,000,000は、1 Mb/秒接続で1秒間に送信できるビット数です。このビット数を800で割ったものがDNS応答のサイズで、この値により1秒間に送信できる応答数が得られます。時間が2秒であるため、応答数に2を掛けます。この値を2の16乗で割ったものが、トランザクションIDフィールドのサイズで、これが1回の攻撃の成功率です。攻撃を成功させるには、x回試行し、そのうちの1回が成功する必要がありますx回の試行を「実験」と呼び、その実験から得られるすべての結果を標本空間(Ω)と呼びます。標本空間で何かが出る確率は間違いなく100%です(これは、式の外側1です)。見込みのある攻撃の確率を求めるには、標本空間全体から「1回以上の攻撃で成功する」以外のもの、つまりx回の試行すべてに失敗するを引く必要があります。1回の試行での失敗は「すべて(1)から1回の試行での成功を引いたもの」であり、1から成功率を引いたものになります。では、Wolfram Alphaで、この式で0.5 (50%)より大きい値が得られるxを求めてみましょう。)

言い換えれば、この緩和策の実施、帯域幅1 Mb/秒の攻撃者は、たった18回の試行で、50%を超える成功率を達成できました。このように、昔はこの破壊的な攻撃をいとも簡単に実施できたのです。

さらなる緩和策

最初は、このような弱い緩和策が選択されたことを意外に思うかもしれません。しかし、DNSプロトコルを完全に再構築することは不可能であったことを思い出してください。インターネットを破壊してしまう可能性がありました。組み込みのリゾルバを持つデバイスだけを考えてください。それらのデバイスが完全に動作を停止する可能性がありました。しかし、それ以上の何かを実施する必要があります。上述したように、トランザクションIDによる緩和策は脆弱でした。

そこで、同じ緩和策を、今度はソースポートで使用しました。DNSクライアント、リゾルバ、ネームサーバーからのDNS要求は、ソースポートから送信されます。この緩和策の前まで、ソースポートはランダム化されていませんでした。

ソースポートはトランザクションIDと同じ16ビットの数値で、その機能のためにのみ使用されます。たとえば、リゾルバがポート10650を使用しようとして、このポートが前の要求で使用中の場合、リゾルバは10651を使用します。むろん、他の用途向けのポートもあるため、16ビットすべてを利用できるわけではありません。

ソースポートとトランザクションIDの組み合わせは、DNS通信のキー(32ビットキー)として使用されます。これは、これから説明する攻撃において重要であるため、覚えておいてください。これらの追加の16ビットにより、攻撃の成功率が著しく減少しました。

以前は、帯域幅1 Mb/秒での成功率は3.8% (Wolfram Alphaでの計算を参照)でした。ソースポートによる緩和策を実施した場合、実施前と同じデータで攻撃が成功する確率は0.00005821%です。実施前と同じ成功率を実現するために必要な試行回数は1,190,820回です。攻撃の成功は確実ではなくなり、事実、ソース ポート ランダマイゼーションによってほとんどのDNSキャッシュポイズニング攻撃は終止符を打たれました。

より高度な脆弱性

これまでは、主として単一のDNSレコード、"www.example.com is at 93.184.216.34" (Aレコードとも呼ばれます)のキャッシュポイズニングについて説明してきました。ルーターやKubernetesクラスタなど、標的のDNSサーバーでレコードを汚染してきました。このアプローチでは、1つのドメインを汚染する難しい攻撃に懸命に取り組んできましたが、もっとうまくやれたかもしれません。

2008年に、セキュリティ研究者のDan Kaminskyがよりよいアプローチを発見しました。これは、本ブログでこれまで説明したアプローチよりも、ずっと効果的なアプローチです。この脆弱性は、セキュリティコミュニティに大騒動を巻き起こしました。一般的な実行方法は、これまで説明した方法とほぼ同じであるため、引き続きトランザクションIDとソースポートを推測する必要があります。成功率はほぼ同じですが、Kaminskyの方法では、成功した場合に、より多くのレコードを汚染することができました。

NSレコード

名前が示すように、ネームサーバー(NS)レコードは、どのDNSサーバーがドメインの権威であるか、つまり、どのサーバーにドメインの実際のIPアドレスが含まれるかを示すDNSレコードです。上記の例では、example.comのNSレコードは、www.example.com、email.example.comおよび?.example.comのアドレスを保持するサーバーです。

ここでの攻撃者の目標は、標的のリゾルバを汚染することです。つまり、標的がwww.example.comemail.example.comまたは?.example.comにアクセスしようとしたときに、リゾルバに要求をexample.comネームサーバーではなく攻撃者のネームサーバーに転送させることです。

この方法によって、攻撃者は以前のようにwwwへのアクセスだけではなく、example.comのすべてのサブドメインへのすべてのアクセスを制御します。

しかし、どうやって?

意外にも、この攻撃は本ブログで説明した前の攻撃とよく似ています。ただし、攻撃者はリゾルバのキャッシュをAレコードで汚染するのではなく、NSレコードでキャッシュを汚染しようとします。

攻撃者はまず、汚染しようとするドメインのネームサーバーを構成します。誰かが自分のネームサーバーを任意のドメインの権威サーバーとして構成することを阻むものは、何もありません。ただし、他のサーバーが権威サーバーを指すことはないため、これは通常は無意味です。

より高度なDNSの脆弱性によって、ここに図示するように、完全なゾーンポイズニング攻撃への道が開けます。ここでは、攻撃者は単一のレコードではなくNSレコードを汚染し、影響はより大きいものになります。
図3: 完全なゾーンポイズニング攻撃

次に、攻撃者は汚染するゾーンのサブドメインに対するDNS要求を発行します。キャッシュされていない、したがってリゾルバが要求をルートサーバーに転送することが分かっているドメインでなければなりません。それだけでなく、この例では、そのドメインのNSレコード、example.comの権威ネームサーバーもキャッシュされていないことが必要です。キャッシュされた場合、リゾルバは要求をルートサーバーにすら転送せずに、その権威ネームサーバーに直接転送します。

攻撃者が、標的のリゾルバに要求をルートサーバーへと転送させた後、ルートサーバーは通常、「答えはわかりません。向こうにあるサーバーに尋ねてください。サーバーのIPアドレスは次のとおりです。」というような内容のNSレコードで応答します。

これが、難しい部分です。攻撃を成功させるには、攻撃者はルートサーバーをしのぎ、ルートサーバーの応答が送信される前に、汚染された応答を配信する必要があります。それには、目的のドメインのNSレコードを含む大容量の応答を送信します。攻撃者がトランザクションID (そして、恐らくソースポートも)を推測する必要があることを忘れないでください。

NS回答にはそれぞれ、「接着剤」レコードが含まれます。これは、ネームサーバーの実際のIPアドレスです。他の方法であったなら、要求元サーバーはネームサーバーをどこで見つけたらよいかわからないでしょう。リゾルバはこの接着剤レコードを受信してキャッシュし、接着剤が指すネームサーバーに問い合わせて、要求されたドメインのアドレスを取得します。

攻撃が成功した場合、以後、リゾルバに送信された、汚染されたドメインゾーン下のキャッシュされていないドメイン(www.example.comexample.com)の要求は、攻撃者のネームサーバーに到達します。

アプリケーション

この高度な攻撃では、攻撃者は同時に多数のドメインを汚染できます。理論的には、攻撃者は.comゾーン全体を汚染することもできます。

.comのような大きいゾーンを汚染するのは困難ですが、攻撃者が実際にキャッシュ全体を汚染しようとした事例もあります。次のような例があります。

こうした攻撃が、毎年何百も実施されています。

緩和策

新しい緩和策は行われませんでした。すでに使用されていたトランザクションIDとポートランダマイゼーションによる緩和策により、この攻撃はすでにほぼ実施不能であったためです。しかし、攻撃者が「通常の」単一のAレコード攻撃ではなく、この攻撃を使用してこれらの緩和策を乗り越えようとした場合、攻撃はより激しいものになります。

以前と同じように、トランザクションIDとポートランダマイゼーションを一緒に使用して、攻撃者が推測できないくらい十分に大きいキーを作成して、攻撃を緩和します。

最新の脆弱性

DNSプロトコルが安全であると考えられる場合でも、開発者はDNSプロトコルを安全に実装できないことがあります。

たとえば、一般に、ランダム化関数は予測不能であるため、暗号化キーの生成に使用できると誤解されています。しかし、ランダム化関数は暗号化またはセキュリティに使用するものではありません。残念なことに、DNSの実装ではしばしば、ランダム化関数の誤用が発生します。

2年前に、CoreDNSでトランザクションIDをランダム化するのに使用された関数で発生しました。開発者は、Golangの非暗号的に安全な疑似乱数ジェネレータであるmath.randを使用することにしました。これは、大きな問題でした。攻撃者がトランザクションIDを予測できる場合、本ブログで説明したように、CoreDNSのキャッシュを確実に汚染できます。これは、CoreDNSをDNSリゾルバとして使用するアプリケーションはすべて、悪意のあるDNSレコードに対して脆弱であることを意味します。CoreDNSは主としてKubernetesに使用されているため、クラスタ全体のどのノードのどのアプリケーションもこの攻撃を受けやすいことになります。

もう1つの例は、数週間前に発見されたばかりの新たな手法です。この手法では、Internet Control Message Protocol (ICMP)を使用して、サーバー上で閉じているポートを正確に特定することで、実際に空いているポートを明らかにします。この手法をDNS攻撃で使用することで、攻撃者が推測する必要のあるポートの数が著しく減少し、前述のポートランダマイゼーションによる緩和策の効果が減少します。

むろん、バッファオーバーフローのようなキャッシュに関連しない脆弱性もあります。dnsmasqは多くのLinuxディストリビューションおよびルーターで使用されている一般的なDNSリゾルバで、Kubernetesの初期のバージョンでも使用されていました。最近では、サービス拒否(DoS)につながる可能性のある大量のメモリの欠陥、リモートコード実行(RCE)などに対してdnsmasqが脆弱であることが判明しました。

結論

本ブログで説明したDNS攻撃は、成功した場合、破壊的な結果を招く可能性があります。最新の緩和策のおかげで、リゾルバアプリケーションの他の脆弱性と組み合わせない限り、攻撃者がDNSポイズニング攻撃を成功させる可能性は低くなりました。残念ながら、こうした脆弱性は、今日たびたび検出されています。

長年にわたって、リサーチャーはUser Datagram Protocol (ユーザー データグラム プロトコル - UDP)フラグメンテーションなど、本ブログで説明したいくつかの緩和策を克服する手法を発見してきました。

それでも、昨今は安全なブラウジングや証明書が使用されているため、攻撃者がドメインのDNSサーバーを汚染しようとしても、証明書が一致しないため、ほとんどの場合ブラウザの標的はページをロードしません。しかし、ISPのDNSリゾルバを汚染し、要求を存在しないIPアドレスに転送するという手法が、大規模なDoS攻撃でいまだに使用されています。

パロアルトネットワークスのお客様は、さまざまな方法によって、本ブログで概説した攻撃から保護されています。パロアルトネットワークスの次世代ファイアウォールは、疑わしいDNSクエリおよび異常なDNS応答を検出することで、DNS攻撃をブロックできます。枯渇または大量のトラフィックに関連する攻撃は、ファイアウォールに組み込まれているDoSまたはゾーン プロテクション プロファイルで対処できます。これらの保護が、DNSに対する脅威への対応およびDNSセキュリティサービスを介したインジケータに加えて提供されます。

さらに、Prisma Cloudで旧式の脆弱なコンポーネントおよびクラウド内の構成が不適切なアプリケーションに関するアラートを送信することで、クラスタを保護できます。本ブログで説明したほとんどの攻撃で、内部からDNSサーバーに悪意のあるパケットを送信するには、クラスタの内部ネットワークを侵害する必要があります。こうした侵害はほとんどの場合、時代遅れの構成が不適切なアプリケーションを悪用して行われます。