はじめに
glibcでヤバメな脆弱性キター!
- 「glibc」ライブラリに脆弱性、Linuxの大部分に深刻な影響 - ITmedia エンタープライズ
- Google Online Security Blog: CVE-2015-7547: glibc getaddrinfo stack-based buffer overflow
- CVE-2015-7547: Critical Vulnerability in glibc getaddrinfo - SANS Internet Storm Center
- Carlos O'Donell - [PATCH] CVE-2015-7547 --- glibc getaddrinfo() stack-based buffer overflo
内容見るに、getaddrinfoの名前朝解決時に悪意あるDNSレスポンスパケット食わされるとexploit発動って…、それほぼ全ての通信を行うアプリが全部ヤバい奴じゃん!!!
てか個人的な話だがタイミングがマジ糞すぎる。昨日深夜から早朝にかけて年に1度の大規模定期メンテナンス弊社オンプレサーバ再起動祭り2016を実施して数100台の物理サーバ再起動を敢行し終わったとこだっつーの!!!!ふッざけんな!まじふざけんな…!
対策(本道)
- 大抵のディストリビューションではもう脆弱性対応版のglibcが出てるから yum とか apt でガンガン上げる!
- ただし glibc 上げたら既存プロセスは再起動してやらないといけないのでまぁ全プロセス考えたら一度OSごと再起動するのが早いわね。
- どうしても再起動したくない場合は最低限外に通信しそうな sshd, nginx, httpd, php-fpm, mysqld, postgresql-server, postfix, dovecot, sendmail, vsftpd, elasticsearch, node, java, docker, etc, etc… あたりは個別に再起動しといたほうがいいかもねっていうかやっぱ全部だからサーバごと再起動するのが早いね!
対策2(邪道、っていうか全台再起動とか大仕事すぎるから少し時間的猶予をくれ!!)
って人は多いと思う。ていうかうちなんてマジそれだしな。あと2日早く言ってくれたらどんなに良かったか…。
DBサーバとかNFSサーバとか参照されてるとこが多すぎる奴とかは大変なので今すぐ再起動とか無理だから少し待って!って人用にとりあえず iptables でぶっ叩けそうなのでその設定を書いておく。
どうやら今回の脆弱性はパケットサイズが2,048バイト以上の場合に成立するらしいので、単純にDNSレスポンスパケットはUDP/TCP共に2048バイト以上のサイズなら捨てるようにしよう!
DNSのパケットで2kbいくようなのなんて普通じゃ殆どありえない(UDPの場合は従来から512バイトって制限あったくらいだしね)からそうそう問題にはならんだろ! まぁDNSSECとかあとDNSサーバ同士のAXFR使ったゾーン転送とかで問題起きるかもだけど。まぁそんくらいか。そういう特殊なとこは適当対策じゃなくてちゃんとやれって話だ!
iptablesの場合
確かパケットサイズでフィルタ出来たような気がするから調べる…。あったあったlengthモジュールだ。↓こんな感じでパケットサイズをレンジ指定で条件付け出来る。
sudo iptables -I INPUT -p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo iptables -I INPUT -p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo ip6tables -I INPUT -p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo ip6tables -I INPUT -p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
パーマネントな設定にしたいなら以下の行を /etc/sysconfig/iptables (ipv6用はip6tables)
の *filter
と COMMIT
の間に追加して、
-A INPUT -p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
-A INPUT -p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
-A INPUT -p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
COMMIT
で、 service iptables restart; service ip6tables restart
で反映だ!
firewalldの場合(CentOS7とか)
iptablesとfirewalldは一緒に使えない(というかfirewalldはiptablesの只のラッパーで、今までiptablesを直叩きしてたのをCLIや設定用のI/Fを変えたものなだけ)なので、firewalldでルールを管理してる場合は、その流儀に従って追加をしてやりましょう。
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP
sudo firewall-cmd --reload
ってことでドーン。
lengthモジュールとか使う場合は普通のadd-ruleのオプションだとできないので --direct
オプションでiptablesのオプションをそのまま書いてルール追加。--permanent
付けると設定ファイルへの追加をしただけでこの時点でまだルール自体は効いていないので、firewall-cmd --reload
を実行してやることで現在動いてる環境へ反映だ!
ちなみに上記を実行すると /etc/firewalld/direct.xml
に以下のようにルールが追加されてるはずだ。
<?xml version="1.0" encoding="utf-8"?>
<direct>
<rule priority="0" table="filter" ipv="ipv4" chain="INPUT">-p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP</rule>
<rule priority="0" table="filter" ipv="ipv4" chain="INPUT">-p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP</rule>
<rule priority="0" table="filter" ipv="ipv6" chain="INPUT">-p udp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP</rule>
<rule priority="0" table="filter" ipv="ipv6" chain="INPUT">-p tcp --sport 53 -m state --state ESTABLISHED -m length --length 2048: -j DROP</rule>
</direct>
時間稼ぎが出来たらあとは頑張る
スケジュール立てて、いっぱいglibc上げて、いっぱい再起動する。以上!
最終的にはやっぱ頑張るのだ。
追記:懸念事項
ふと思ったんだが、getaddrinfoが処理する層でのDNSのレスポンスサイズって話だと、パケットサイズに制限かけても細かいパケットに分割されてきたのが上の層に来るまでに大きく再構築された場合とかだとすり抜けちゃうかもかな…? まぁ気休め程度と思ってやっぱしちゃんとglibc入れ替える方向は頑張りましょう。これだけやってとりあえず安心して根本放置するのはやめてねーと。
追記:MTUでフラグメント化したら2048バイトのパケットとか来ないんじゃね?って懸念について
MTUでフラグメント化したらパケットは全部2048バイト以下で意味なんじゃね?ッて思ったのでとりあえずstateを足してみた。state有効だとip_conntrackが読み込まれてフラグメント化されたパケットをPREROUTING辺りでデフラグメントしてからINPUTチェーンに来るんだった気がするので、多分filter時点ではMTUの影響は気にしなくて良くなるはず。まぁそこまで詳しく理解してない部分も多いからやっぱり気休めかもしれず。