_ きのうの dnsops BoF で発表してきました。 資料置いときます。
_ スライドに書いてないこと、当日話さなかったことについていくつか補足。
- こういう危険な文字をリゾルバがどのように扱うべきかの規定は、探したかぎりでは見つからなかった(たぶんどこにも規定されてないと思うんだけど確証はない)
- BIND8 のコードは 4.9.7 にバックポートされ、BIND4 も最終的にはサニタイズ処理するようになった
- つまり、OpenBSD はバックポート以前のコードをずっと保守している
- OpenBSD については近所のコミッターさんに直接報告済み
- courier については開発者にメールで報告済み
_ OSX や OpenBSD、Windows などのリゾルバが脆弱だとはいえないと思う。glibc など BIND8 由来のコードは応答を勝手に加工しているわけで、本来やるべきでない余計なことをやっているという解釈も成り立つわけだし、BIND8 のコードもサニタイズ対象の文字が非常に少なく、shellshock はたまたま防げたけれど、他の攻撃にも耐えられるとはとても思えない中途半端な実装でしかない。OpenBSD の getnameinfo() はそのへんしっかり対策してあって、こちらの関数を使っていればまず攻撃は成立しないだろうけど、基本的にはリゾルバに頼らず、呼び出す側の方で対策すべきだと思う。
_ 質疑で getdnsがどうなってるか聞かれたけど答えられなかったので、調べてみた。getdns-0.1.5/src/gldns/wire2str.c の dname_char_print() という関数。
ちうことで、.;()\ の記号5種と、0x00-0x20、0x7f-0xff。libc の dn_expand() と比べると "@$ の3文字少ない。ただし、この関数がどこから呼ばれてるのかまで追いきれてないので、どんな場合もこれが呼ばれて危ない文字が排除されるのかどうかは確認できてない。ちなみに ldns も ldns_rdf2buffer_str_dname() という関数で同じ文字を潰してる。if(c == '.' || c == ';' || c == '(' || c == ')' || c == '\\') return gldns_str_print(s, slen, "\\%c", c); else if(!(isascii((int)c) && isgraph((int)c))) return gldns_str_print(s, slen, "\\%03u", (unsigned)c);_ おまけ。逆引きに 0x00 (NUL) が含まれていた場合の各 OS の getnameinfo() の挙動。foo.example.com.(NUL)bar.example.net. という逆引きを DNS につっこんだ。
NUL 以降を捨てるリゾルバは DNS に存在していたものとまったく異なる名前を返しているわけで、これによって逆引きを誤認させることができると言えなくもないんだけど、そもそも逆引き自体がいくらでも詐称可能なので、別にできたからといって通常の逆引き詐称とと大差ない。これをもってセキュリティ的に危ないことができるわけじゃないと思う。
- Linux(glibc)、FreeBSD: 名前解決失敗
- NetBSD: NUL が \000 にエスケープされて返ってくる(foo.example.com.\000bar.example.net)
- dig や drill の出力もこれと同じ
- OpenBSD、OSX: NUL 以降が切り捨てられる(foo.example.com)
- Windows: NUL 以降が切り捨てられる(foo.example.com.)
- getnameinfo() じゃなくて、ping -a の出力
- OpenBSD や OSX と似ているが、末尾に "." がつく
- これは FQDN 末尾の "." とは異なるもの(逆引きに細工していない場合には末尾の "." がない)
_ せっかくの3連休なのに、急用ができてどこにも行けなくなってしまった。今年は晴天の週末が貴重なのに…。