PowerDNSで 遊 んでみた 坂 口 俊 文 ( 個 人 参 加 ) DNS Summer Days 2015 2015/07/24 資 料 公 開 のため 修 正
概 要 PowerDNS Security Advisory 2015-01 (CVE-2015-1868/CVE-2015-5470) https://doc.powerdns.com/md/security/powerdns-advisory-2015-01/ http://jprs.jp/tech/security/2015-04-27-powerdns-vuln-decompression.html 以 下 の2 点 についてアナウンス 4/23: 特 定 のプラットフォーム(RedHat/CentOS 5.x)にて 細 工 したドメインを 用 意 し そのドメインに 関 するクエリを 送 信 することで PowerDNSがクラッシュ 5/1, 7/7: すべてのプラットフォームにて 細 工 したクエリを 送 信 す ることで サーバのCPU 負 荷 が 急 上 昇 本 日 は 後 者 について 発 表 します
自 己 紹 介 坂 口 俊 文 2 年 前 までは ISPのメール DNS サーバの 管 理 者 現 在 はとあるクラウドサービスのサポート? 本 日 は 個 人 参 加 Twitter: @siskrn GitHub: https://github.com/sischkg/ PowerDNS 歴 : ちょうど3ヶ 月 (2015/7/24 時 点 ) 最 近 ここに 名 前 が 乗 りました
経 緯 (CVE-2015-1868) 4/23 にセキュリティアドバイザリが 公 開 内 容 は "ドメイン 名 の 圧 縮 の 展 開 の 不 具 合 により 特 定 のプラットフォーム (RedHat/CentOS 5.x)にて プロセスがクラッシュ " であるため 検 証 していたところ "CentOS 6.6でもPowerDNS Recursor 3.6.2でCPU 使 用 率 が 上 昇 " となる 現 象 を 発 見 PowerDNSの 開 発 元 へ 報 告 (4/29)
影 響 (CVE-2015-1868) 細 工 したUDPクエリをPowerDNSへ 送 信 ひとつのクエリで 論 理 CPUのひとつの 使 用 率 が100% そのクエリが PowerDNSのひとつのThreadを 占 有 (1,2 分 ) Thread 数 のクエリで PowerDNSは 無 応 答 に
影 響 (CVE-2015-1868) 一 定 間 隔 でクエリを 受 信 したときのCPU 使 用 率 4 論 理 CPUサーバ CentOS 6.6 PowerDNS Recursor 3.6.2
対 象 の 環 境 (CVE-2015-1868) アドバイザリには 記 載 はないが 影 響 が 出 る 環 境 と 出 ない 環 境 が 存 在 影 響 がでた 環 境 PowerDNS Recursor 3.6.2 CentOS 5.11, 6.6 PowerDNS Authoritative Server 3.4.3 CentOS 6.6 影 響 がでなかった 環 境 PowerDNS Recursor 3.7.1 CentOS 6.6
原 因 (CVE-2015-1868) PowerDNS Authoritative Server 3.4.3/Recursor 3.6.2 では 以 下 の 問 題 が 存 在 するため CPU 負 荷 の 上 昇 が 発 生 ドメイン 名 の 長 さが 無 制 限 256 文 字 以 上 もエラーにならない ドメイン 名 の 圧 縮 を 展 開 する 際 の 問 題 自 己 参 照 で 無 限 ループ ループ 対 策 のリミッタが 大 きい(1000) DNSメッセージ 内 のドメイン 名 を 文 字 列 に 変 換 する 際 の 余 計 な 処 理 std::string::reserve storm.
原 因 となるSource Code(CVE-2015-1868) pdns/dnsparser.cc 内 のメンバ 関 数 void PacketReader::getLabelFromContent(... ) DNSメッセージ 内 のドメイン 名 文 字 列 https://github.com/powerdns/pdns/blob/rec-3.6.2/pdns/dnsparser.cc https://github.com/powerdns/pdns/blob/auth-3.4.3/pdns/dnsparser.cc
getlabelfromcontentの 処 理 getlabelfromcontent (... string &ret... ) { // retは 処 理 後 のドメイン 名 の 保 存 先 if 再 起 呼 び 出 しが1000を 超 えた { 例 外 をthrow } for(;;) { DNSメッセージ 内 のドメイン 名 から1byte(ラベル 長 )を 取 得 if ラベル 長 == 0(ドメイン 名 を 全 て 処 理 ) { 終 了 } if ラベル 長 & 0xC0 (ドメイン 名 圧 縮 ) { if 前 方 参 照 { 例 外 をthrow } 参 照 先 を 指 定 して getlabelfromcontentを 再 帰 呼 び 出 し } } } ラベルを 取 得 し retへ 追 記
std::string::reserve storm Authoritative Server 3.4.3およびRecursor 3.6.2では ひと つのラベルを 読 み 込 む 前 に 一 回 std::string::reserveを 呼 ぶ Recursor 3.7.1では std::string::reserveを 削 除 済 み
std::string::reserve storm この 一 行 を 追 加 するだけで 実 行 時 間 に 致 命 的 な 差 が 発 生 Auth 3.4.3/Recursor 3.6.2 Recursor 3.7.1 std::string ret; for ( int i = 0 ; i < MAX ; i++ ) { ret.reserve( ret.size() + n ); ret.append( label ); ret.append( "." ); } std::string ret; for ( int i = 0 ; i < MAX ; i++ ) { } ret.append( label ); ret.append( "." ); 実 行 時 間 : 45.5 秒 >>>> 実 行 時 間 : 0.01 秒 * 実 行 環 境 :CentOS 5.11 on VirtualBox; CPU Core i5 2510M 2.5GHz * std::string::reserveの 実 装 によっては 結 果 が 異 なる
対 策 (CVE-2015-1868) PowerDNSの 開 発 元 は 以 下 のバージョンへのアップグレードもしくは パッチの 適 用 を 推 奨 PowerDNS Authoritative 3.4.4 PowerDNS Recursor 3.6.3 PowerDNS Recursor 3.7.2 修 正 内 容 ドメイン 名 の 長 さ 制 限 ドメイン 名 圧 縮 の 展 開 の 不 具 合 std::string::reserve Auth 3.4.3 3.4.4 制 限 なし 自 己 参 照 を 禁 止 再 帰 制 限 1000 100 有 Recursor 3.6.2 3.6.3 制 限 なし 自 己 参 照 を 禁 止 再 帰 制 限 1000 100 有 Recursor 3.7.1 3.7.2 制 限 なし 自 己 参 照 を 禁 止 再 帰 制 限 1000 100 元 から 無
ドメイン 名 の 長 さ 制 限 の 欠 如 の 影 響 修 正 内 容 において ドメイン 名 の 長 さを 制 限 していなかったため もう 少 し 調 査 PowerDNS Authoritative Server 3.4.4で 問 題 を 発 見 PowerDNS 開 発 元 に 報 告 (5/22) CVE-2015-5470
ドメイン 名 の 長 さ 制 限 の 欠 如 の 影 響 (CVE-2015-5470) Authoritative Server 3.4.4に 対 して TCPで 細 工 したDNSク エリを 送 信 ドメイン 名 圧 縮 は 関 係 なし
影 響 (CVE-2015-5470) ひとつのクエリで 論 理 CPUのひとつの 使 用 率 が100% ( 複 数 のクエリで 複 数 のCPUの 使 用 率 が 上 がることはない) 他 のTCPのクエリの 処 理 が 停 止 (UDPのクエリには 影 響 なし) バックエンド(MySQLなど)へ 大 量 のクエリ PowerDNS Authoritative 3.4.4 以 下 で 発 生 PowerDNS Recursorでは 影 響 なし
PowerDNS Authritative Serverの 処 理 Client TCPクエリ 1 他 のTCPのク エリの 処 理 が 停 止 TCPNameServer PacketHandler 2 論 理 CPUのひ とつの 使 用 率 が 100% PacketCache UeberBackend Backend 3バックエンドへ 大 量 のクエリ http://blog.powerdns.com/2015/06/23/what-is-a-powerdns-backend-and-how-do-i-make-itsend-an-nxdomain/
他 のTCPクエリの 処 理 が 停 止 TCPクエリの 場 合 PacketHanderへ 処 理 を 移 す 前 にLockを 取 得 する そのため その 処 理 が 終 わるまで 他 のTCPクエリは Lockの 開 放 を 待 つ https://github.com/powerdns/pdns/blob/auth-3.4.4/pdns/tcpreceiver.cc
論 理 CPUのひとつの 使 用 率 が100% 1. 最 初 にqnameのSOAをPacketCacheから 検 索 2. 見 つからない 場 合 は 先 頭 のラベルを 削 除 してもう 一 回 3. 見 つけるまで 繰 り 返 す 4. 最 後 まで 見 つからなければ バックエンドから 検 索 www.example.co.jp example.co.jp co.jp jp さらに PacketCacheではドメイン 名 を 次 のように 変 換 して 扱 う www.example.co.jp 2 'j' 'p' 2 'c' 'o' 7 'e' 'x' 'a' 'm' 'p' 'l' 'e' 3 'w' 'w' 'w'
バックエンドへの 大 量 のクエリ 1. qnameのsoaをバックエンドから 検 索 2. 見 つからない 場 合 は 先 頭 のラベルを 削 除 してもう 一 回 3. 見 つけるまで 繰 り 返 す www.example.co.jp example.co.jp co.jp jp Backendへ 大 量 のクエリが 発 生
対 策 (CVE-2015-5470) PowerDNSの 開 発 元 は 6/9に 新 バージョンをリリース PowerDNS Authoritative 3.4.5 PowerDNS Recursor 3.6.4 PowerDNS Recursor 3.7.3 Bug fixes: Limit the maximum length of a qname 修 正 内 容 ドメイン 名 の 長 さ 制 限 ドメイン 名 圧 縮 の 展 開 の 不 具 合 std::string::reserve Auth 3.4.3 3.4.5 制 限 なし 制 限 あり (1024 未 満 ) 自 己 参 照 を 禁 止 再 帰 制 限 1000 100 あり なし Recursor 3.6.2 3.6.4 制 限 なし 制 限 あり (1024 未 満 ) 自 己 参 照 を 禁 止 再 帰 制 限 1000 100 あり あり Recursor 3.7.1 3.7.3 制 限 なし 制 限 あり (1024 未 満 ) 自 己 参 照 を 禁 止 再 帰 制 限 1000 100 なし なし
まとめ 以 下 のバージョンのPowerDNSは 外 部 からの 攻 撃 によりサービ スが 停 止 Authoritative Server 3.4.4 以 下 Recursor 3.6.2 以 下 Exploitは 簡 単 に 作 成 可 能 アドバイザリやパッチの 内 容 で 十 分 作 成 可 能 CVE-2015-1868 3 時 間 かかりませんでした CVE-2015-5470 MySQL(バックエンド)のクエリログを 見 れば