memcachedを安全に運用するポイント
2010年8月10日のスラッシュドット・ジャパンにて「Memcached に潜むセキュリティホール」としてmemcachedの脆弱性に関する記事が上げられました。記事の内容をまとめると以下の2点となります。
bit.ly や Globworld、Gowalla といったサイトではインターネットから memcached へのアクセスが可能であった
アクセスしたmemcached上にユーザーのログイン ID / パスワードが格納されており参照可能だった
http://slashdot.jp/security/article.pl?sid=10/08/10/0052240
ここには2つの問題があったと考えます。1つ目はmemcachedをインターネットから接続可能な状態で設置してしまったこと、もう1つはキャッシュ上に置かれている必要はなさそうなパスワードやメールアドレスがmemcachedに保存されていたということです。
あまり知られていることではありませんが、memcachedの統計機能を使うことでキャッシュされているキーの一覧が取得でき、そこからすべてのキャッシュオブジェクトをダンプすることができます。攻撃者からすれば、memcachedにつながりさえすればそこに保存されているあらゆるデータは入手可能になります。
memcachedを安全に運用するためには、設置するネットワークと保存するキャッシュオブジェクトの内容に注意を払う必要があります。
memcachedを限られたネットワークに設置する
memcachedは、Webアプリケーションの高速化のためにシステムのバックエンドで動作するキャッシュサーバとして設計されています。データセンターのプライベートネットワークなど、限られたネットワークだけでアプリケーションサーバと通信を行う前提で作られており、認証の機能、アクセスの制御の機能はつい最近のバージョンまで搭載されていませんでした。そのためグローバルIPアドレスを持つサーバ上でmemcachedを起動し、意図せずインターネットに対してキャッシュデータを公開をしてしまうことは多くあります。
特定のインターフェースのみListenする
グローバルIPアドレスからの接続を防ぐには、グローバルIPアドレス側のポートをListenしないのがもっとも簡単です。memcachedでは -l オプションを使うことで任意のIPアドレスだけをListenすることができます。
$ memcached -l IPアドレス
IPアドレスに「127.0.0.1」指定するとローカルホストからのみ参照可能となり、外部のサーバからアクセスができなくなります。プライベートネットワークを指定するには
$ memcached -l 192.168.x.x
などとします。ListenしているIPアドレスを確認するには、netstatコマンドを使うとよいでしょう
$ sudo netstat -nlp | grep memcached
tcp 0 0 192.168.67.254:11211 0.0.0.0:* LISTEN 16166/memcached
udp 0 0 192.168.67.254:11211 0.0.0.0:* 16166/memcached
tcp、udpともにプライベートネットワークのIPアドレスのみListenしていることが確認できました。
ただし、 -l オプションにはIPアドレスを1つしか指定できないので、プライベートIPアドレスとローカルホストといった2つ以上のIPアドレスをListenすることはできません。そのようなユースケースではLinuxのiptablesなどOSのIPパケットフィルタ機能を使うと良いでしょう。
SASL
クラウド環境など、多数のサーバが同じネットワークを共有する場合にはListenするIPアドレスを制限したり、iptablesでのフィルタリングだけでは対応が困難になってきます。そのようなネットワーク上でmemcachedを運用する場合には memcached 1.4.3でサポートされた SASL(Simple Authentication and Security Layer)による認証を利用できます。
SASLを使用するには、memcachedをsaslオプションを有効にしてビルドし直す必要があります。
$ ./configure --enable-sasl
また、アスキープロトコルではSASLが利用できずバイナリプロトコルが必須となります。加えてSASLをサポートするmemcachedクライアントがまだ一部に限られるので、普及にはもう少し時間が掛かりそうです。
不必要なデータをキャッシュしない
限られたネットワークにmemcachedを設置し、SASLで認証を行ったとしても、ひとたびmemcachedへの侵入を許せば、サーバ上のすべてのキャッシュオブジェクトが閲覧可能な状態になります。不正なアクセスによる被害を大きくしないための最も簡単な対策として、不必要なデータをmemcached上に置かないことが挙げられます。
とくに筆者が注意すべきだと考えるのはO/Rマッパーとmemcachedの組み合わせです。O/Rマッパーの中にはデータベースへの問い合わせを透過的にmemcachedにキャッシュする機能を持つものがあります。そのような透過型のキャッシュを利用した際に、パスワードやEメールアドレスを含んだユーザテーブルのレコードをそのままmemcachedにも保存してしまうことが考えられます。意図しないデータをmemcachedに保存することでデータ漏洩のリスクも高くなり、加えてキャッシュの空間効率も悪化します。安全にmemcachedを運用するためには何をキャッシュするのかという視点も重要となります。
memcached injection
memcachedにはmemcachedクライアントから不正なキャッシュ名を送信することで任意のコマンドを注入(injection)することが可能となる脆弱性があります。2008年にNTTコミュニケーションズ株式会社より、memcached Injectionについての資料が発表されています。
memcached Injectionについての資料(NTTコミュニケーションズ)
http://www.icto.jp/security_report/pdf/sr20080310.pdf
memcachedの主流なプロトコルはアスキープロトコルです。アスキープロトコルでは改行で区切られた平文のテキストをサーバとクライアントとでやりとりを行うことでキャッシュオブジェクトの保存取得を行います。このプロトコルの利点としてtelnetを使って簡単にmemcachedの操作することができるなど、人間から見たときのわかりやすさが挙げられます。しかし、命令文やキャッシュ名の途中に改行やスペースを入れることでプロトコルを騙す事ができ、Injectionの機会が生まれます。
キャッシュのキーに改行を入れることによるInjection
キー名に改行を入れることによるInjectionの例です。Perlのクライアントを想定しています。
$memd->get("foo\r\nset bar 0 0 4\r\ntest");
getコマンドでキャッシュオブジェクトを取得するソースコードですが、その際にキャッシュのキーに改行を含め、その次にsetコマンドとなる文字列を追加しています。上記のコードを実行したときのmemcachedサーバの様子を -vv オプションのログで確認します。
<30 new auto-negotiating client connection
30: Client using the ascii protocol
<30 get foo
>30 END
<30 set test 0 0 4
>30 STORED
<30 connection closed.
(1)で本来のgetコマンドが発行されていますが、途中で改行が入っているため、( 2)で命令が終了したのち、( 3)でsetコマンドが実行され、( 4)でsetが成功しキャッシュの保存ができていることが確認できます。
実際にsetなどのキャッシュの操作コマンドが発行されなくても、改行コードやスペースなど不正な文字列が入ることで意図する動作にならない可能性があります。このInjectionを防ぐにはキャッシュのキーに対してURI Escapeなどを行い、サニタイズしてからmemcachedクライアントに渡す対策が有効です。以下はPerlでの例です
use URI::Escape;
my $s_key = uri_escape( $key, "\x00-\x20\x7f-\xff");
$memd->get($s_key);
キャッシュのキーの長さによるInjection
改行コードなどの制御コードによるInjectionの他に、memcachedのプロトコル上決められている250文字より長いキーを使用するとInjectionを引き起こすことができます。
$memd->set("x"x251,"delete test");
上記のソースコードでは251文字のキーにて、deleteから始まるテキストを保存しようとしています。実際に動かして、サーバ側の動作をみると
<30 set xxxxx(…略…)xxxxx 0 0 20
>30 CLIENT_ERROR bad command line format
<30 delete test
>30 DELETED
<30 connection closed.
(1)で251文字のキーを受けたことでエラーを発生していますが、クライアント側はかまわずdelete命令が含まれる次の行を送信してしまっているため(2) 、( 3)で削除が完了しています。このInjectionを防ぐにはキーの文字列の長さを確認するのが最も有効です。また一定以上の文字数のキーを自動的にhash値に置き換えてしまう手段もあります。
if ( length $key >= 240 ) {
$key = Digest::MD5::md5_hex($key);
}
$memd->set($key, "delete test");
上記の例では、240文字以上のキーを自動的に32文字のMD5のhash値に入れ替えます。キーが250文字より大きくなる心配はないので、setの第二引数に渡されたdeleteは実行されることはありせん。
改行コードや文字数オーバーによるInjectionは、JavaやPythonなど一部のクライアントライブラリでは対策がすでにされていますが、その他の言語のライブラリではキーの確認などを行っていないため、Injectionが起きる可能性があります。memcachedを利用したアプリケーションを開発運用している方は、Injectionが発生しないかを一度確認する必要があると思われます。
Injection対策としてバイナリプロトコルを使う
memcachedのinjectionはすべて、アスキープロトコルでの制約上生まれた脆弱性なので、バイナリプトコルを使い、アスキープロトコルを利用しなければ問題は起きにくくなります。アスキープロトコルを無効にするにはmemcachedの起動オプション -B を使います。
memcached -B binary
-B オプションでbinaryを指定することでバイナリプロトコル以外を受け付けなくなります。デフォルトはautoで、アスキープロトコルとバイナリプロトコルを自動判定します。
まとめ
今回はmemcachedを運用する上で確認が必要なセキュリティと脆弱性について書かせていただきました。memcachedはシンプルで非常に高速に動作しますが、認証機構やセキュリティといった側面では機能が不足していると思われるような部分があります。実際の運用ではここで紹介したような脆弱性対策についても気を配ることが求められます。次回は安定運用に欠かせないmemcachedの監視について紹介します。