DNS権威サーバのクラウドサービス向けに行われた攻撃および対策 〜後編〜
この記事は、2023年1月25-27日(水-金)に行われたJANOG51における発表を編集部にて記事化したものです。
目次
前編のあらまし
さくらインターネットのSRE室で室長を務めている長野です。
前編の記事では、DNSサーバへの攻撃手法や、実際に発生したさくらのクラウドのDNSアプライアンスへの攻撃の様子を紹介しました。それに続く本記事では、このような攻撃に対してどのような対策を行ってきたかを紹介します。
水責め攻撃への対応と対策
ではここから、最初の攻撃が去年の夏にあってから、どういう対応と対策をしてきたのかを紹介したいと思います。
スタンバイ側のVRRPデーモンの停止
初回を思い出すと、CPU負荷が非常に上がり、100%近いCPUを使うようになって名前解決が遅延し、タイムアウトしたというのが、最初のアラートとして上がりました。
その中でよくよく調べると、VRRPで冗長化をしているのですけれども、その切り替えがパタパタ発生していたんですね。PowerDNSが落ちてしまった、タイムアウトしたというので切り替わります。ところが、切り替わると、またそちら側でも同じようなことが起きて切り戻されます。こういうパタパタが連続して起きると名前解決ができない時間も増えていきます。
そこで、とりあえずの対応としては、消極策ではありますが、スタンバイ側のVRRPのデーモンを停止することをまずやりました。そうすることで、落ちることには変わりありませんが、パタパタしない分だけ多少マシになると思いました。最初の攻撃が夜間の12時過ぎだったので、もうできることがなく、収束するまで何時間か待つということが行われました。なので長時間にわたり影響が出たということがありました。
iptablesによる対策
それでは2度目以降にどういう対策を行ったかというのが次のスライドですが、まずその場でできることとして、攻撃に対する経験があったメンバーがiptablesを使った対策を入れました。これはのちほど紹介します。
あともう一つ、CPU負荷が上がって停止したのであればCPUを強くすればいいだろうということで、サーバのスケールアップも行いました。このDNSサーバ自体もさくらのクラウドのIaaS上で動いていますので、1回サーバを停止する必要はありますが、サーバを停止してプランを変更して起動すれば簡単にスケールアップができるので、対応を行いました。さらに、JANOG39の発表で紹介したDDoSのMitigation装置、これも導入しました。最後にソフトウェアの改良ということで、すぐにできるPowerDNSやMariaDBのチューニングを行いました。
次にiptablesの対策はどんなことを行ったかというと、iptablesで特定のパターンにマッチしたパケットを捨てるということをやりました。設定内容をスライドに示します。
iptables -I INPUT 14 -i eth0 -p udp --dport 53 -m string --hex-string "|076578616d706c6503636f6d000001|" --algo bm --from 41 --to 512 -j DROP -m comment --comment "*.example.com:a:udp"
上記の行の--hex-stringに続く16進で表現したパケットの中身、076から始まるものが、example.comに対するクエリのパケットですね。UDPとTCPの両方で来ていたので、設定が2行あって、1行目がUDP、2行目がTCPです。
このときの攻撃に遭ったゾーンの設定としては、Zone Apex(ネイキッドドメイン)のexample.comとwwwが付いたものの2つしか登録されていなかったので、これは良かった点ではあるのですけれども、ドット(.)example.comは拒否しつつ、その中でもwwwが付いたものについては許可するという設定を入れました。
iptables -I INPUT 14 -i eth0 -p udp --dport 53 -m string --hex-string "|777777076578616d706c6503636f6d000001|" --algo bm --from 41 --to 512 -j ACCEPT -m comment --comment "www.example.com:a:udp"
同様に7777で始まるのがwwwのところですね。このようにパケットごとに中身を見て、iptablesの設定で、攻撃ではないものだけを通すみたいなことをしました。これで、最初の段階としての攻撃を多少かわすことができるようになりました。
PowerDNSのTCP接続のチューニング
ただ、これはPowerDNSのチューニングの話になってくるのですけれども、iptablesだとかDDoSのMitigation装置を導入することで、予想していなかった影響が出たというのがあります。
DNSサーバの手前、ポート53で動いているサーバの手前でパケットを落としちゃうので、サーバ側にTCPの接続だけが残るという状況が生まれました。そうするとサーバ側は次のパケットが来る、あるいはクローズするのをずっと待ってしまうので、TCPの最大接続数、PowerDNSではデフォルト値が結構低いのですが、この最大数を軽く超えてしまって、TCPでの名前解決だけができなくなりました。UDPは全然問題なく動いているので、TCPだけ名前解決できないのはなぜだろうと最初悩んだのですけれども、この点に気づいて、Linuxのmax openfilesの設定を緩和しつつ、PowerDNSのTCP接続のチューニングを行いました。TCPコネクションの最大数を1500ぐらいにした上で、リクエストが来なかったら1秒でどんどん切っていくみたいな設定を入れて、このときは乗り切りました。
もう一つ、PowerDNSがダウンする原因は何かというところですが、PowerDNSのバックエンドへの問い合わせがある程度たまってしまうと、PowerDNSが自動でダウンする、自分で自分をkillするということが行われます。max-queue-lengthという設定項目がそれですが、デフォルトが5000なんですね。バックエンドへのクエリが5000個たまると、PowerDNSが自分で死んじゃうんですね。たいていの場合はsystemdによって再起動されるのですが、その間接続不能になり、名前解決ができません。
このmax-queue-lengthを増やす実験をしていくと、2万ぐらいに設定することで落ちにくくはなるのですが、レイテンシはかなり悪くなります。これはどうしたものかと。落ちた方がいいのかどうか非常に悩ましいところで、現在はデフォルトのまま5000で運用しております。MariaDBが固まるということも実際考えられるので、ちょっと安全側に倒した設定にしています。
対策の改善
iptablesを入れて、DDoSのMitigation装置を入れて、PowerDNSのチューニングをして、あと今回の発表では紹介していませんがMariaDBのチューニング、これは自分がもともとWebサービスの運用をやっていて得意だったのでやりました。しかし、これだけではまだ対策が足りていない部分がありました。
先ほどのDNSのtcpdumpのときに、大文字小文字が混じったクエリがあるという話をしました。GoogleのパブリックDNSから来るクエリですね。こういったものはiptablesでは防ぐことができません。全部のパターンをiptablesに入れる、つまりexample.comのすべての文字が大文字小文字のパターンを入れるみたいなことは難しいです。よって、大規模な攻撃が来ると影響を受けてしまう可能性があります。
もう一つ、導入したDDoSのMitigation装置は、攻撃を検知するとそのゾーンに対して丸ごとレートリミットがかかるような設定で運用しておりました。そうすると名前解決ができない時間帯が生まれてしまい、お客様への影響が避けられないという問題があります。実際、ずっと攻撃が来ていて、攻撃が成功してもしなくてもずっと来ているので、攻撃者の狙いというものが何なのかまったくわからなくなってきているのですけれども、おそらく名前解決できなくなるというのが攻撃者の狙いだと思うので、そういったことが回避できるかどうかという点で微妙だなと思っておりました。
それから、iptablesを入れていると、お客様がexample.comとwww.example.comの他に、例えばapi.example.comみたいなものを追加したときに、こちら側でiptablesを変更しない限りはそれに対する名前解決ができなくなってしまいます。そうするとお客様は「なぜだ」となってしまうので、名前解決ができないことに対して、カスタマーサポートから事前に連絡を行っておりました。
そういったこともありまして、example.comではなく別のドメインに攻撃が来たらどうするのかとかも含めて対策の改善をする必要があるということで、サーバ側でクエリの中身をもうちょっと細かく見て判断できるものを入れたらいいんじゃないかということで、dnsdistというものを入れました。もう一つ、攻撃が来たということをなるべく早く検知して対応することが必要だったので、モニタリングの改善を行っております。これらの対策を次に紹介します。
dnsdistの導入
dnsdistは、PowerDNSの開発元がリリースしている、わかりやすくいうとDNS用のプロキシサーバになります。ロードバランサとして働いたり、フィルタだとかQPS制限をかけたりすることができる、ちょっと便利なプロキシサーバです。
これをどう入れたかですけれども、今まで53番ポートで既存のPowerDNSが動いていて、3306番ポートでMariaDBが動いているというサーバ上に、PowerDNSのポートを変えて、dnsdistを53番で動かします。このdnsdistからローカルのPowerDNSに対してプロキシするような構成に変更しました。これで、お客様からのクエリはdnsdistでまず受け取って、問題ないかどうかを確認した上でPowerDNSに送るという構成になりました。
どんなフィルタの設定になっているのかというのが、こちらのスライドです。上の方は53番でlistenするよというのと、newServerで、背後に1053番ポートで動いているPowerDNSにプロキシするよという設定になります。
下半分よりちょっと上からが実際のフィルタの設定ですけれども、example.comというドメインに対して、OrRuleでQTypeがAレコードもしくはAAAAの問い合わせを指定します。NotRuleという行は、example.comとwww.example.comではない場合ですね。
さらにMaxQPSIPRuleという行がありますが、1つ目の引数が、実際にかけるQPS、QueriesPerSecondですね。2番目の引数(16)がネットマスクになります。これはどういうことかというと、/16のネットマスクの単位で3QPS以上という意味です。これが24だったら、/24の単位で3QPSということになります。それ以上のクエリが来たら、最後のDropActionというところでdropしてしまうように設定しています。
example.comとwww.example.comを入れているので、正しいサブドメインでのクエリは影響を受けないことになります。もちろんdnsdistは大文字小文字関係なく扱えるので、example.comのxが大文字だったとしても、問題なくこれで扱えるという設定になっています。
dnsdistを入れるにあたって、これ自体が問題にならないように確認したのがこのスライドになります。
dnsperfという負荷をかけるツールもあるのですが、ランダムなサブドメインを作成してクエリを投げるという部分を自分で作ってみたかったので、導入前にprsd-benchという負荷掛けのツールをGo言語で作って確認しました。
dnsdistにdropする設定を入れると名前解決がタイムアウトするまで待つしかなくなってしまうので、一番最後のDropActionのところでrefuseを返すという設定ができるので、その設定を入れて名前解決のベンチマークを行いました。
そうすると大体16万QPSぐらいはrefuseを返すことができました。これはCPUが4コアの場合ですね。なので、1分間で960万クエリはさばけることを確認しております。実際のサーバはもうちょっとたくさんCPUが乗っているものを使っているので、これ以上さばけるねということを確認しております。実行結果を見ると、実際に2QPSがresolvedと書いてあるので、2QPSだけがちゃんと返ってきて、refusedが16万以上あるという感じですね。こういうテストをした上でdnsdistを実際のサーバに導入して、いま運用しております。
Mackerelを利用したモニタリング
もう一つ、はてなブログで有名なはてなのサービスであるMackerelという監視のサービスがあります。弊社でもこれを使ってサーバのメトリクス収集をしていて、何か問題があればここからアラートを上げるようなことをやっているのですけれども、こちらでこのPowerDNSおよびdnsdistの監視のプラグインを自分で作りまして、サーバで動かしています。こちらについてはOSSとして公開することにして、自分のGitHubのアカウントで2つとも公開しております。
さらに、いろいろなメトリクスの中で、MariaDBだとかLinuxそのもの、例えばいろいろなTCPの受信数だとか、新しく来たというパケットの数みたいなところも含めてさまざまなメトリクスを収集して、何か攻撃の検知ができないか、あるいは攻撃を緩和するとか避けるためのものができないか、みたいなことを調べております。
何かしら攻撃があったときにアラートを上げることが必要になってくるので、攻撃の検知時はSlackに通知を行っています。バックエンドに行ったクエリ、PowerDNSからMariaDBに対して1分間に行われたクエリの数をずっと記録しておりまして、それがある程度の数を超えたらSlackへ通知するようなことをやっています。
サービス上問題なくても一応攻撃が来ているということがわかったら、こういったアラートが上がるようなことをやっています。もし本当にサービスに影響があるようなことがあったら、運用チームの方がそれに気づくことができるので障害対応にあたることになります。
もし、今まで来ていなかったゾーンへの攻撃があれば、dnsdistの設定、先ほどのexample.comへの攻撃を防ぐようなものを新たに作って、Ansibleでそれを投入する手順を作って共有しております。攻撃からそれを投入するまで、どんなに急いでも15分ぐらいはかかってしまうので、まだまだ改善しないといけないなというのは思っております。
「DNS水責め攻撃」対策の難しさ
最後に、水責め攻撃の対策の難しさについてお話しします。
パブリックDNSからやってくるというのが、まず1つ目の非常に難しいポイントです。他のゾーンの名前解決が当然あるわけです。自分たちでやっておりますCDNサービスの名前解決だとかも当然含まれます。なので、単純なQPS制限、100QPS以上は受け付けないみたいな設定というのは非常にやりにくいです。あと、ゾーン単位でのQPS制限は、先ほどもありましたけれども、サービス拒否攻撃につながるので、攻撃者の狙いを回避できないというのもあります。
では全部dnsdistに入れていけばいいかというと、dnsdistの設定が膨らむとどうなるかはまだ検証していませんが、おそらくこのホワイトリストが大きくなるとdnsdist自体の負荷が大きくなってくるんじゃないかなと予想しています。
あともう一つの問題は、ワイルドカードの扱いが困難なことです。*.example.comというものをお客様が登録していたらどうなるのかということです。どんなクエリが来るのかわからずフィルタを書くことができないので、難しい問題として残っております。
今後の対策案
まだまだ問題はあるので、今後の対策でどんなことを考えているかですけれども、MySQLをやめるというのが一番手っ取り早いかなと。といっても結構大変なのですけれども、やめることも検討しております。他のLMDB、例えばKVS(Key-Valueストア)だとかBIND形式にすると、7倍~8倍以上速くなっちゃうんですね。こういったベースの性能が上がることで水責め攻撃に強くなります。
あともう一つ、クラウドネイティブの考えを取り入れて、負荷分散のためにオートスケールをしたりということも検討できるかなと思っています。
それから、サービスとしては、攻撃に対する対応、どういうことが起こりますよというSLAの明記ですね。これも必要なのかなと思っていたりします。
あと、反映時間の許容範囲の緩和を考えています。マニュアルにはだいたい1分間ぐらいで反映しますみたいなことが書いてあるのですけれども、それをもうちょっと緩和すると、上記した他の形式に変えるような対策がやりやすいかなと思っています。
参考までに、PowerDNSのバックエンドをMySQLとLMDBとBIND形式にしたときのベンチマーク結果を掲載します。横軸がラベル数、縦軸がQPSです。ラベル数が0のときはキャッシュにすべてヒットするのでほぼ差がありません。ラベル数が増えると3形式ともQPSは落ちていきますが、MySQLに比べて他の形式では性能低下が小さくなっています。
まとめ
まとめにいきます。
本発表では、まずさくらのクラウドのDNSのアプライアンスの構成を説明させていただきました。それから、実際に発生した水責め攻撃およびその対策の紹介と、DNSの水責め攻撃はこんなことが対策として難しいということを紹介させていただきました。
この発表を通して皆さんと議論したいこととして、まずdnsdistは日本語のリソースがあまりないんですね。PowerDNS自体を運用されている方は結構多いのですけれども、運用ノウハウを持っている方がいたら教えて欲しいなと思います。それから、このDNSの水責め攻撃に対して皆さんどうしているのかなっていうのをいろいろ知りたいと思っていますので、よろしくお願いします。最後に、攻撃元となるオープンリゾルバ側で何かやってもらえないのかについて、情報をお持ちの方がいたら教えていただきたいなと思っています。ご清聴ありがとうございました。
おわりに
JANOG51 Meeting in Fujiyoshida にて発表する機会をいただきありがとうございます。スタッフの皆様、発表を現地やオンラインで聞いてくださった方、弊社のブースに足を運んでいただき感謝しております。
発表では昨年からさくらのクラウドのDNSサービスで発生している水責め攻撃および、その対策について紹介させていただきました。発表後の議論の時間やブースにてお話しをさせて頂き、まだまだやらなければならないことが多くあると痛感しています。
発表中にもありますが、DNSはインターネットにおける基盤技術の一つであり、高いパフォーマンスと堅牢性が求められます。今後とも頂いたコメントを参考にして、お客様に価値を届けられるサービス開発、運用に取り組んでいきます。