ホスティング事業部MREチームでインフラエンジニアをやっている原口です。
先日、弊社の DNSサービスに対し、軽めの DDoS攻撃が来たので、その際に対応した手順を簡単にご紹介します。
DNSサービスに対する DDoS攻撃への対応について
DNSサービスに対する DDoS攻撃は昔からあり、弊社でも対策を行っております。
拠点や回線を分け、冗長化を行うのはもちろんですが、各拠点で「DDoS軽減装置」と言われるアプライアンスを導入しています。これは、不正なパケットを DROP をするものですが、一般的なファイアウォールのようなルールベースではなく、学習結果をもとに、通常とは異なる傾向のアクセスがあると動作するものになっています。
今回紹介する対応は、この DDoS軽減装置をすり抜ける程度の、軽めの DDoS攻撃に対して行ったものです。
DNSサービスの構成
今回 DDoS攻撃が来たサービスでは、先ほど紹介した「DDoS軽減装置」の下に、Linux で構築された「ロードバランサー」があり、その配下にある「PowerDNS」で DNSサービスを提供しています。
リゾルバや攻撃者のクエリは、ロードバランサーを通ることになるので、ここでどういった攻撃が来ているか調査することになります。
DNSクエリの調査
ロードバランサーについても、他のサーバーと同様に Prometheus や Grafana を使用し、システムリソースの可視化は行われていますが、DNSクエリの解析まではできておりません… このため、どういった攻撃が来ているかは、直接ロードバランサーで確認するしかないので、いきなり tcpdump を実行します。
# nオプションは、名前解決を行わない。これがないと全然表示されません... 以下の内容はサンプルです。
$ sudo tcpdump -n port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
(snip)
10:13:03.228667 IP a.X.X.X.37981 > Y.Y.Y.Y.domain: 22146+ [1au] A? example.com. (39)
10:13:05.027223 IP b.X.X.X.38109 > Y.Y.Y.Y.domain: 60557+ [1au] A? example.com. (39)
10:13:06.003453 IP c.X.X.X.37915 > Y.Y.Y.Y.domain: 12269+ [1au] A? example.com. (39)
10:13:06.582140 IP d.X.X.X.38230 > Y.Y.Y.Y.domain: 13272+ [1au] A? example.com. (39)
^C
コンソールに大量に出力されますが、DDoS攻撃なので、ぱっと見で当たりがつきます。ここで当たりがつかない場合は、53番ポート以外へのアクセスかもしれないので、tcpdump のオプション変更などを行い、引き続き調査が必要となります。
今回は、DNSフラッドのような通常クエリが大量に来ていました。ここで、対象のドメインが特定できたので、このドメインに対するクエリを、ロードバランサーの iptables で DROP していきます。
DNS のパケット
対象ドメイン(例:example.com)のクエリを DROP したいのですが、特定の IPアドレスやポート番号をまとめて DROP するわけにはいかないので、iptables の string モジュールを利用します。string モジュールを使うと、特定の文字列を含むパケットを DROP できるようになります。
このような感じで登録したいところですが、残念ながら、これでは DROP できません。
# これでは DROP できない
iptables -I INPUT -m string --string example.com --algo bm -j DROP
以下のようにする必要があります(下記コマンドは string のためのサンプルです。実際に登録したチェーンやルールは異なります)。
iptables -I INPUT -m string --hex-string "|076578616d706c6503636f6d|" --algo bm -j DROP
ご存知の方もいると思いますが、DNSパケット内のドメイン名(QNAME)は、複数のラベルに分けられ、ラベル長とラベルデータのセットを連結したフォーマットになっています。
example.com が、2つのラベル(example と com)に分けられ、それぞれのラベルデータ(「6578616d706c65」と「636f6d」)の前に、ラベルデータのバイトサイズ(「7」と「3」)が挿入されている。
このため、string モジュールで登録する内容は、ラベル長を含めた内容にしなければならないのですが、私は ruby のワンライナーで作成しています。1
$ echo example.com | ruby -lne '$_.split(".").each{|w|printf("%02x%s",w.length,w.unpack("H*").first)}' ; echo
076578616d706c6503636f6d
ここで出力された内容を、前述のように hex-string オプションで登録すると、無事 DROP されます。
最後に
私の経験内の話ですが、最近、DNS に対する DDoS は減り、直接ウェブサイトに対する攻撃が増えたような気がします。
また、DNS に対する DDoS であってもアンプ攻撃などは、DDoS軽減装置の方で処理してしまうので、今回のようにロードバランサーで対応するのは数年ぶりでした。 めったにやらない対応なので、共有しておけば誰かの役に立つかと思い、ブログに記載した次第です。
私が所属する MRE(Messaging Reliability Engineering *ペパボの造語です)チームは、SRE で行うような取り組みを、DNS やメールなどのメッセージングサービスに適用するために組織されたチームです。 サービスの性質上、他社のサーバーと協調しながら動作するため、なかなか自分たちが理想とする振る舞いを設計するのは難しいのですが、そこがやりがいでもあります。こういった仕事に興味があるかたは、ぜひご連絡ください。
-
初回掲載時、ワンライナースクリプトが誤っていたため、修正しました。申し訳ございません(ラベルデータのサイズを16進数ではなく、10進数で表示していました)。 ↩