Redis Lua scripting: several security vulnerabilities fixed

Salvatore Sanfilippo

Redis Luaスクリプティング:複数のセキュリティ脆弱性を修正

1か月と少し前、Apple Information Securityチームからメールが届きました。監査の過程で、同チームがRedisのLuaサブシステム、具体的にはcmsgpackライブラリにセキュリティ上の問題を発見したという内容でした。このライブラリはLua本体の一部ではなく、私自身が書いたMessagePackの実装です。機能を拡張するプルリクエストをマージする際に、セキュリティ上の問題が混入してしまいました。その後、同じチームがLuaのstructライブラリでも新たな問題を発見しました。これもLua本体のものではありません。少なくとも私たちが使っているLuaのバージョンではそうで、Redisユーザーが利用できるLuaインタプリタに機能を追加するために、ソースコードを埋め込んでいたものに過ぎません。その後、私自身が同じstructパッケージで別の問題を見つけ、さらにAlibabaのチームがcmsgpackやLua APIを使用する他のコードパスで多数の問題を発見しました。こうして短期間のうちに、Lua関連の脆弱性の山を抱えることになりました。

これらの脆弱性が特に関係するのは、クラウド上でマネージドなRedisサーバーを提供しているケースです。発見された脆弱性は、Redisサーバーへ直接アクセスできなければ悪用される可能性が極めて低いからです。多くのRedisユーザーはcmsgpackやstructパッケージ自体を使っておらず、使っている場合でもそこに信頼できない入力を渡すことはまずありません。しかし、クラウドプロバイダーでは事情が異なります。プロバイダーは、時にはマルチテナント構成で、サービスを契約したユーザーに対してRedisインスタンスを公開しています。ユーザーはそうしたRedisインスタンスに任意のデータを送りつけることができ、脆弱性を突いてメモリを破壊し、Redisプロセスを侵害し、さらにはプロセスを完全に乗っ取ることさえ可能です。

例えば、このシンプルなPythonプログラムは、cmsgpackの脆弱性の1つを使ってRedisをクラッシュさせることができます [1]。

[1] https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a

一方で、自身のインスタンスに何を送るかをコントロールできている通常のRedisユーザーから見れば、リスクは限定的です。struct.unpack()のような関数に信頼できないデータを渡し、しかもフォーマット引数で「bc0」のような特に危険なデコードフォーマットを指定した場合に限られます。

アドバイザリの調整

Apple Information Securityチーム、私、そしてRedisのクラウドプロバイダーの皆様の協力と友好的なコミュニケーションのおかげで、脆弱性の公開前に主要なRedisプロバイダーすべてに連絡を取り、公開前にシステムへパッチを当てられるよう、リリースの調整を試みました。プロバイダーが簡単に適用できるよう、単一のパッチを提供しました。そして昨日から今日にかけて、セキュリティ修正を含めたRedis 3、4、5の新しいパッチリリースを用意しました。このブログ記事を読まれている時点では、すでにすべてリリースされています。残念ながら、小規模あるいは新しいクラウドプロバイダーには連絡を取ることができませんでした。Redis Labs、Amazon、Alibaba、Microsoft、Google、Heroku、Open Redis、Redis Greenとのコミュニケーションだけでも膨大な作業で、情報共有の範囲を広げればリークのリスクがさらに高まります(各社で多くの関係者が対応にあたっていました)。もし本日初めてこの脆弱性を知ったRedisプロバイダーの方がいらっしゃれば、申し訳ありません。できる限りのことは尽くしたつもりです。

この問題に関するヒントやご支援をいただいたApple Information Securityチーム、そしてすべてのプロバイダーの皆様に感謝申し上げます。

Luaが抱える問題

正直なところ、RedisのLuaエンジンを設計した当初は、顧客とクラウドプロバイダーが対立するようなセキュリティモデルは想定していませんでした。Redisサーバーを触る人は信頼できる、というのが暗黙の前提でした。そのため、Luaライブラリについてもセキュリティの観点から厳密に精査してはいませんでした。当時の感覚としては、RedisのAPIにアクセスできるのであれば、そもそももっと重大なことができてしまう、というものでした。

しかしその後、状況は変わりました。クラウドプロバイダーはマネージドなRedisインスタンスを提供できるよう、顧客に公開するRedisのAPIを制限するようになりました。CONFIGやDEBUGといったコマンドは拒否される一方で、EVALやEVALSHAまで遮断することは現実的にできません。RedisのLuaスクリプティングは、コミュニティで最もよく使われている機能の1つだからです。

そうして私自身も気づかないうちに、Redisの公開・提供のされ方が変化する中で、Luaライブラリもまた、本来Redis側で対処すべきセキュリティモデルにおける攻撃経路となっていきました。前述のとおり、このモデルで影響を受けるのはRedisユーザーというより、マネージドなRedisの「クラウド」プロバイダー側ですが、いずれにせよ対処すべき問題です。

Luaスクリプティングにまつわるこの問題について、クラウドプロバイダーのセキュリティの現状を改善するために何ができるでしょうか。今後数か月で取り組みたいことをいくつか挙げます。

  1. Luaスタックの保護です。Luaは、多少のパフォーマンス低下と引き換えに、LuaスタックAPIの誤用を防ぐ形でコンパイルできるようです。正直なところ、Luaのスタックに関する前提は少々安易すぎると感じています。ライブラリの開発者は、新しい値をプッシュするのに十分な空きがスタックにあるかを常にチェックしなければなりません。同じ抽象度にある他の言語では、C APIにこのような問題はありません。そこで、Luaの低レベルC APIにより多くの安全策を加えた場合の速度低下が許容範囲かどうかを検証し、可能であれば実装したいと考えています。
  2. セキュリティ監査とファズテストです。時間は限られていましたが、すでにLuaのstructライブラリでファズテストを実施しています。今後もこの領域で他のバグがないかチェックする活動を続けていきます。まだまだ多くの問題が潜んでいるはずで、今回見つかった一連のバグだけがすべてではないのは、スクリプティングサブシステムを調査する時間がそれ以上なかったからに過ぎません。ですから、これは今後実施する重要な活動になります。今回と同様、活動が終わり次第、Redisベンダーと連携して適時にパッチを当てられるようにします。
  3. Redisユーザーの視点からは、信頼できないデータがLuaエンジンに送られる際に、そのデータが改ざんされていないことをHMACで保証することが重要です。例えば、ユーザーの状態をユーザー自身のCookieに保存し、後でデコードするというよくあるパターンがあります。そのようなデータが後にRedisのLua関数の入力として使われることがありますが、こうした場合、以前保存したものを確かに読み取っていることを保証するために、HMACは絶対に必要です。
  4. さらなるLuaのサンドボックス化です。このトピックについては文献やベストプラクティスが豊富にあるはずです。すでに一定のサンドボックス化は実装していますが、セキュリティに携わっていた頃の感覚からすると、サンドボックス化は結局のところイタチごっこであり、完璧に行うことはできません。例えばCPUやメモリの濫用は、Redisの目的からすると追跡が複雑すぎるかもしれません。ただ少なくとも、違反が起きてもメモリ内容の破壊を伴うことなく、適切に中断されることは保証すべきです。
  5. Luaエンジンをアップグレードする時期なのかもしれません。新しいバージョンのLuaがセキュリティの観点でより優れているのかどうかは分かりません。ただ、Luaをアップグレードすると古いスクリプトが動かなくなる可能性があるという大きな問題があります。Redisコミュニティにとってこれは非常に深刻な問題で、特にRedisユーザーが通常作成するようなスクリプトにとっては、より新しいLuaバージョンの利点はごくわずかだからです。

修正された問題

修正された問題は、以下のコミットに記載されています。

  • ce17f76b Security: fix redis-cli buffer overflow.
  • e89086e0 Security: fix Lua struct package offset handling.
  • 5ccb6f7a Security: more cmsgpack fixes by @soloestoy.
  • 1eb08bcd Security: update Lua struct package for security.
  • 52a00201 Security: fix Lua cmsgpack library stack overflow.

最初のコミットは今回の取り組みとは無関係で、コマンドラインで長いホスト引数を渡した場合にのみ悪用可能なredis-cliのバッファオーバーフローに関するものです。その他の問題は、cmsgpackとstructパッケージで見つかった問題です。

問題を再現するための2つのスクリプトは以下のとおりです。

https://gist.github.com/antirez/82445fcbea6d9b19f97014cc6cc79f8a

および

https://gist.github.com/antirez/bca0ad7a9c60c72e9600c7f720e9d035

いずれもApple Information Securityチームによるものですが、最初のスクリプトはより確実にクラッシュを再現できるよう、私が修正を加えています。

影響を受けるバージョン

基本的には、Luaスクリプティングを備えたすべてのRedisが影響を受けます。

修正は、以下のGithubタグとして提供されています。

  • 3.2.12
  • 4.0.10
  • 5.0-rc2

安定版リリース(4.0.10)は、いつもどおり http://download.redis.io でも入手できます。

リリースのtarballのハッシュは、こちらで公開しています。

https://github.com/antirez/redis-hashes

なお、今回リリースされたバージョンには他にもさまざまなバグ修正が含まれています。そのため、新しいバージョンに切り替えることで他にどのような変更が入るのか、リリースノートにも目を通すことをお勧めします。

今後、RedisのLuaスクリプティングサブシステムに対して予定しているセキュリティ監査の報告とともに、またブログ記事でお届けできればと思います。

原文は Salvatore Sanfilippo により に公開されました。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。