目次
■クラウド&インフラストラクチャ
2019.10.30 2024.02.20 約4分
本連載では、インターネット基盤技術、または、インターネットのインフラ技術と呼ばれる領域に関して、Webホスティングサービスの歴史やWebサーバの設計と実装を中心に執筆していきます。第7回では、前回同様セキュリティの話について、更に踏み込んでいきます。
連載一覧:まつもとりーのインフラ入門
DSO実行方式は、動的コンテンツとして実行したいプログラムをApacheで実装するために、Apacheモジュールとして当該インタプリタをApacheのサーバプロセスに組み込み、サーバプロセスがプログラムを直接実行するため、一般的にCGI実行方式と比較して、リクエスト時にプロセスの生成と破棄やインタプリタのロードが必要なくなり性能が高くなります。
代表的なDSO実行方式の実装として、mod_phpやmod_perl、mod_rubyなどが挙げられます。筆者が実装しているmod_mrubyやngx_mrubyも基本的には同様のアーキテクチャになります。
また、スクリプトの処理を渡すインタプリタを指定するために、スクリプトの行頭に記述するシェバン行や権限を細かく設定する必要もありません。
しかし、Apacheに組み込まれて実行される以上、基本的にはApacheのサーバプロセス権限で実行されるため、以前紹介した下図と同様の他ホストが覗き見される問題が生じます。
以下、DSO実行方式を安全に利用するためのセキュリティ機構としてこれまでに提案されているものを紹介するとともに、それらの課題について論じます。
DSO実行方式において、広く使われているDSO版PHP(モジュール名はmod_php)は、他ホストの領域を閲覧できないようにするため、セーフモードという機能がありました。セーフモード機能を利用すると、DSO版PHPであっても他ホスト領域のファイルを覗き見することができません。
しかし、PHP特有のセキュリティ機構でありOSやミドルウェアのレイヤーからみたときには汎用性が低いこと、共有サーバ上のOSやファイルシステム上のセキュリティ問題をPHPのアプリケーションのレイヤーで解決しようと試みるのはアーキテクチャ及び実装上非常に複雑になり、あまり筋が良くないといった理由から、PHP5.3.0で使用が非推奨となり、PHP5.4.0では削除されました。
DSO実行方式を採用した場合でも、mod_suid2というモジュールを利用すると、他ホスト領域の閲覧を防ぐことができます。
mod_suid2は、Apacheのサーバプロセスをroot権限で起動しておき、リクエストを処理する度にsetuid()およびsetgid()システムコールによりユーザ権限に変更します。これによって、Apacheの権限とは別の権限でプロセスを実行できるため、suEXECと同様、他ホスト領域を閲覧できなくできます。
しかし、処理後はサーバプロセスが一般ユーザ権限であるため、権限を元のroot権限に戻すことができません。そのため、ユーザ権限に変更されたサーバプロセス、より厳密にはレスポンスを生成するワーカプロセスはコンテンツ処理後に破棄する必要があります。
その結果、ワーカプロセスを再利用できず、リクエスト処理毎にワーカプロセスを再起動し直す必要があるため、DSO実行方式を利用していたとしても、ワーカプロセスの再生成はsuEXEC用のプロセスの再生成よりも初期化処理のコストが大幅に高いため、suEXECよりも性能が大きく低下します。
また、セキュリティの観点からは、サーバプロセスをrootで起動させていると、万一サーバプロセスそのものに任意のコマンドを実行できるなどの脆弱性があった場合や、設定ミスによってサーバプロセスの権限でコンテンツを実行してしまった場合に、悪意のあるユーザが容易にrootの特権を得られるという問題もあります。
原らによる提案手法では、サーバプロセスをrootで起動し、セキュアOSの機能でrootの権限を一部制限した状態で、リクエスト毎にfork()システムコールでプロセスを新規で生成し、新規生成したプロセスの権限を変更してからリクエスト処理を行う手法を提案しています。
しかし、DSO実行方式と比較した場合に、リクエスト毎にプロセスの生成と破棄が必要となり、依然として性能が低下する問題が残ります。
鈴木らは、アクセスするクライアントが正確に特定できるようなイントラネットの環境において、ユーザ権限であってもsetuid()システムコール等を実行可能にする手法を提案しています。
この手法は、UNIXにユーザ権限でオーナを変更できる新たなシステムコールを実装し、identプロトコルを利用せずに、IPオプションを用いてクライアントプロセスの認証情報を送ることと組み合わせることにより、identプロトコルに依存しないイントラネット内での透過的なクライアントプロセスとサーバプロセス間の権限分離システムを構築可能です。
しかし、信頼のあるクライアントとネットワークが前提となっており、不特定多数のクライアントには対応していないという前提があります。
mod_ruid2というモジュールを利用すると、一時的にユーザ権限で起動しているサーバプロセスに、rootの特権を細分化したLinux Capabilityと呼ばれる機構の内、CAP_SETUID、CAP_SETGIDの特権を与えられます。
下図にmod_ruid2の詳細なアーキテクチャを示します。
特権を与えられたサーバプロセスは、root権限で実行されていなくても、setuid()およびsetgid()システムコールを実行可能となります。その後、mod_suid2同様にApacheのサーバプロセス自体を任意のuid、gidに権限変更してから処理を実行し、再度、元のuid、gidに戻します。
この仕組みによって、DSO実行方式であっても、PHPスクリプトは権限が異なるため他ホスト領域を閲覧できません。また、実行後でも、元のサーバプロセスの権限に戻すことで、プロセスの再利用も可能にしているため、DSO実行方式の性能を維持できます。
しかし、このようなプロセスは、rootのように全ての特権を持たないものの、setuid()およびsetgid()システムコールを実行できる特権を保持していることになります。すなわち、それが意味するところは上図におけるindex.phpのようなWebアプリケーションの脆弱性をつかれ悪意のある者に乗っ取られた場合、setuid()およびsetgid()システムコールによる権限変更を利用し、他ホスト領域のファイル閲覧や変更および不正プログラムの配置や配布等が可能となります。
つまり、サーバプロセス自体に権限を変更できる特権の保持を許すことは、同時に数多くの脆弱性を許すことになります。
一方で、setuid()およびsetgid()システムコールを実行した後にCAP_SETUIDおよびCAP_SETGIDのCapabilityを放棄し、処理後にプロセスを復帰できないように改修すれば安全になりますが、やはりそれではワーカプロセスが再利用できなくなり、mod_suid2同様性能は著しく低下することになります。
一般に、サーバプロセスにアクセス制御を設定後に再度解除するというアプローチは性能上の利点を得られますが、共有型の大規模Webホスティング基盤のセキュリティを考える上でリスクが非常に大きく、脆弱性をつかれた場合の利用者や閲覧者への被害は甚大であり、避けるべきと考えられます。
原らは、Webサーバからの権限変更を可逆的に変更可能にしながら、実行されるプログラムからは権限を変更されないように、プログラムから実行されるシステムコールをフック、つまり、一連の処理の中で特定の処理フェーズが呼ばれた時に、同時に、あるいは、代わりに別の処理も実行できるように予め処理を登録しておくことによって、プログラムから実行される権限変更の処理を無効にしてセキュリティを担保する手法を提案しています。
しかし、Linuxにおいては、システムコールを適切にフックするためにはLinuxカーネルに直接変更を加える必要があり、可搬性が低く、カーネルやライブラリを継続的に更新することが求められる現場において運用上の問題になることが多くなります。
今回も高集積マルチテナントアーキテクチャを採用したWebサーバにおいて、これまで述べてきた基礎概念から少し踏み込んで、マルチテナント環境において非常に重要となるセキュリティの研究動向を前回同様様々な観点から紹介し、その課題をまとめました。いよいよ次回は、今回整理した課題をどのように解決するか、果たして可能なのか等を中心に、引き続きセキュリティについてより踏み込んでいきたいと思います。是非読者の皆様も解決する方法を考えてみると面白いかもしれません。
本連載は下記の私が執筆した論文を参考に、新しい読者へ広めるために平易な形へと再編集しています。
・ 松本 亮介, 栗林 健太郎, 岡部 寿男, Webサーバの高集積マルチテナントアーキテクチャと運用技術, 電子情報通信学会論文誌B, Vol.J101-B, No.1, pp.16-30, Jan 2018.
・ copyright©2018 IEICE
(記事:松本 亮介)