2013年09月18日

レスポンス比較によるDNS Anti-spoofing

Twistedの勉強がてら、並列にN回(以上)クエリをして、結果がTTLを除き一致することを確認するスプーフィング対策を考えてみた。 (商用DNSアプライアンスとかでは既に実装されてたりするんだろうか?)

前提

現在の主なDNSのspoofing手法は、リゾルバの使うUDPソースポート番号やクエリ先ネームサーバーを様々な方法で予測あるいは強制して、クエリIDがヒットするまで偽のパケットを送り続ける。 ポート番号のようなDNSの外側に付加された追加エントロピーを攻略されると、DNSパケットのクエリIDは16ビットしか無い。しかもしばしば攻撃者はブラウザやメーラー経由で能動的に好きなタイミングでクエリを発生させることができる。そのため、DNSSECなどで保護されていないDNSレスポンスは比較的短時間で攻撃成功率が現実的になる。 そのため、0x20のような追加エントロピーや、攻撃を検出してTCPで問い合わせる、といった方法があるようだ。

クエリIDの長さが足りないのなら、複数回クエリして、その全てが同じ内容であることを確認すればいい。N個のレスポンスを比較すれば、その全てのパケットが擬装されている可能性は2^(-16*N)になる。 つまり、N個の相同なレスポンスを得るまでリゾルバ側がクエリをリトライする。(あるいはTCPにフォールバックする)

以下の場合、クエリIDを長くすることでスプーフィングを防ぐ事ができる。

  • リモートのDNSサーバーは信頼出来る
  • リモートまでの経路は確率的には信頼出来る
  • 以下の場合は対象外
    • 通信経路が攻撃者に掌握されている(認証されていないWLANやホームルーター、経路ハイジャックなど)
    • ネームサーバーが攻撃者に掌握されている
    • DNSクエリIDが予測可能
    • IPフラグメントが予測可能(恒常的な2ndフラグメンテーションハイジャック)
    • TCPシーケンスが予測可能

利点

  • クライアント側実装ですむ。サーバーは変更なし
  • 攻撃パケットを監視する必要がない
  • DNSSECに比べれば軽い

欠点

  • ラウンドロビンでレコードが随時変わる場合には組み合わせが発散するので、相同かチェックできない。
    • この場合通常のTCP resolverにデグレードしてしまう。
  • UDPクエリ負荷がN倍程度になる
  • エラーレートと遅延が大きくなる
  • TCPクエリが増える

ラウンドロビン

ほとんどの場合ではN回のリクエストで済んでしまうが、AnycastやGeoDNSやラウンドロビンなど、レスポンスRRsetがそもそも変化する状況では、リクエスト数がより大きくなる。たとえばN=2として所要パケット数は、レスポンスの組み合わせの平方根程度になる。

TCPへのフォールバックを使わない対処法としては、レスポンス全体ではなく、リソースレコード単位で比較して、共通部分のみをレスポンスとして再構成する方法も考えられる。 ネームサーバーがm>>1種類のリソースからk個のレコードをランダム抽出してラウンドロビンしていると仮定すると、m!/k!大雑把にm^k通りのレスポンスが得られ、2回一致するには(m^k)^(1/2)程度のリクエストが必要になる。一方、個々のRR毎に二回一致すればいいのであれば、 (m/k)^(1/2)程度で一つは共通するRRが見つかる。 リゾルバが得られるRRsetが変化してしまうのでRFCに反するが、少なくともA/AAAA/NSレコードではリゾルバはそのうちの一つを任意に選んで使って良いので実用上の問題にはならない。(RRSetの完全性が必要なタイプは個別に実装するか、DNSSECを使う) このRR単位で比較するdegradedな場合、だいたい4回クエリすれば、 k*4^2サーバー程度のRRラウンドロビンから共通のレコードを探すことが出来る。たとえばdig google.com A は11レコードを返すので、背後でバランスされているIPアドレスが160程度なら4〜5クエリで解決出来ると期待できる。

多段のCNAME

一番の問題になりうるのは多段階のCNAMEでバランシングされている場合で、c段CNAMEの連鎖が発生している場合、その全てに一つずつレスポンスを得るためにはm^(c/2)のオーダーのクエリが必要になってしまう。この手法を取り入れようとすると、実質毎回TCPでクエリすることになり効率が悪い。

実際には局所性を高めるために(GeoIPなど)、クエリソースIP範囲に対してある程度決まったレスポンスを返すだろうから、極端に悪くなることはなさそうだが。

TTL

この方式でもレスポンスのうちTTLは攻撃者が1/2^16で操作可能になる。 ランダムにレスポンスの一つからTTLを採用してしまうと、極端に短いTTLによるDoSと、極端に長いTTLによるDoS・リバインディングを排除できない。

短いTTLでのDoSは単にリトライすれば(RR内容の改竄はこの検査を通らないので)若干の遅延だけで解決できるので、検査したレスポンスのうち最小のTTLを採用してしまうのがよさそう。

負荷

ネームサーバー側の負荷はリトライ数倍になるが、上限リトライ数を数回でキャップするのは通常のクエリと同じなのと、Androidのようにブラウザの同一ページ並列ロードですらキャッシュせずパラレルにクエリする実装もあるのでこの方法自体がDoSとみなされることも無いだろう。BINDのRRLでリミットされていても遅延をかけたリトライなら成功する。(クライアント側が攻撃対象のDoSなら防ぎようがないが)

truncateされないクエリに対してもTCPでクエリする場合が増えるので、TCP負荷は増加する。今のところ(truncateの必要のない)TCPクエリをrefuseするネームサーバーは少ないだろうが、ルートネームサーバのように問題になるかもしれない。 最悪の場合、TCPを使わず標準準拠なUDPクエリを(と透過な最後のリトライ)採用することになる。

スタブリゾルバ実装

Twistedのスタブリゾルバ実装をモンキーパッチしてminimalなフォワーダを実装してみた。並列クエリとリトライのみで、RRsetの編集やTCPへのフォールバックは実装していない。 (フルリゾルバ出口用にUDPプロキシとして書きなおしているけれどいつになるやら……)

N=2で通常通りのブラウジングしてみたところ、8.8.8.8にフォーワードした場合で大雑把に5%ほどの追加クエリが発生した。

retrystats: [1100, 42, 5, 1, 2] #リトライ数0からインデックスした配列

リトライはだいたいTTLが短い上に複数のCDNを使っているplatform.twitter.com

自分で運用している単独のリゾルバ相手なら(キャッシュテーブルが一つなので当然だが)キャッシュが消える瞬間以外エラーはでていないようだ。Unboundの自動リキャッシュ機能を使えばゼロに出来るだろうか?

少なくともこの方法でリモートのリゾルバとローカルの間でspoofingを受ける心配はなくなると思われる。

ラベル:DNS
posted by ko-zu at 22:49| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック
×

この広告は90日以上新しい記事の投稿がないブログに表示されております。