4.7. 自然言語(ロカール)の選択

コンピュータが増加し、インターネットが身近になるにつれて、プログラムで 複数の言語や文化をサポートすることが強く求められてきています。 言語とその他の文化に関連した要素のことを普通「ロカール(locale)」と言い ます。 複数ロカールに対応するためプログラム修正する過程を「国際化 (internationalization)」(i18n)と呼び、特定のロカール情報をプログラム に提供することを「地域化(localization)」(l10n)と言います。

全般的には国際化は良いことですが、この過程でセキュリティを侵害する機会 がさらに追加されます。 信頼できないユーザが、望ましいロカール情報を提供できてしまえます。つまり、 ロカールを選択する際に、指定したものと異なるロカールを入力してしまえます。 きちんと防御していなければ、これが悪用されてしまう可能性があります。

4.7.1. ロカールを選択するには

ローカルで起動されるプログラム(setuid や setgid したプログラムを含む)では、 環境変数がロカール情報を提供します。 つまり他の環境変数すべてと同じように、使用する前に選択してから、正しい パタンに反していないかチェックしなければなりません。

Web アプリケーションは、この情報を Web ブラウザから入手します(Accept-Language 要求ヘッダ経由で)。 しかし、ブラウザがすべて正確にこの情報を渡してくるわけではないので (ユーザすべてがブラウザを正しく設定しているわけではないので)、思って いるほど役に立ちません。 Web ブラウザが言語を要求する場合、たいていはただフォームの値として 渡すだけです。 つまり、他のフォームの値と同様に、これらの値は使う前に正しいかどうかチェック しなければいけません。

ロカール情報は、どちらのケースにおいても、先のセクションで議論した入力という 意味でまさに特殊なケースです。 しかし、この入力はほとんど考慮されていないので、あえて独立して論じました。 特に書式文字列(後で論じます)と組み合わさると、ユーザが管理している文字列に よって他のプログラムで任意の命令や不正なデータを動かしたり、その他不適切 な動作を攻撃者が実行できたりします。

4.7.2. ロカールを動かすメカニズム

ロカール・メッセージを選択する方法として、Unix ライクなシステムには大きく 言って 2 つのライブラリ・インタフェースがあります。1 つは「catgets」、 もう 1 つは「gettext」です。 catgets のアプローチは、すべての文字列はユニークな番号が割り振られていて、 その番号をメッセージが書いてあるテーブルのインデックスとして使っています。 一方 gettext のアプローチでは、文字列(通常は英語)を使って、テーブルにある 文字列を翻訳したものを捜します。 catgets(3) は規格として認められていて(X/Open Portability Guide の 3 号 と Single Unix Specification で)、 プログラムで利用可能です。 「gettext」のインタフェースは、公式の規格ではありませんが(しかしもともと は UniForum の提案でした)、インタフェースとして catgets より利用されて いると思っています(Sun や GNU のすべてのプログラムで)。 【訳註:UniForum については、 http://www.uniforum.org/ を見てください】

原理的には catgets の方がわずかに速いはずですが、最近のマシンであれば その差はほんのわずかです。また、catgets() が一意の識別子を維持・管理する のが面倒で、gettext() のインタフェースの方が使いやすくなっています。 私としては、gettext()を使用することをお薦めします。これは使いやすいからに 他なりません。 しかし私の言葉をそのまま鵜呑みにしないでください。gettext については GNU の ドキュメント(info:gettext#catgets) で、たっぷりいろいろと比較していますので、 見てください。

catgets(3)(とそれと関連している catopen(3))はセキュリティ上の問題に対して とても脆弱です。それは環境変数である NLSPATH を使用して、国際化された メッセージを取得するファイル名を管理しているからです。 GNU C ライブラリは NLSPATH を setuid や setgid したプログラムでは無視 するようになっています。これは役には立ちますが、他の実装で動作する プログラムを防御できませんし、そのような防御が必要とは「見えない」 その他のプログラム(CGI スクリプトのような)も防御できません。

広く利用されている「gettext」のインタフェースは、少なくとも私の知る限り、 悪意を持って設定した NLSPATH に対して脆弱ではありません。 しかし、悪意を持って設定した LC_ALL や LC_MESSAGES は、問題を起こすように 思えます。 また、gettext の cat-compat.c にある bindtextdomain() ルーチンを使うと NLSPATH に頼ることになります。

4.7.3. 正しい値

とりあえず、信頼できないユーザに希望するロカールを設定させるなら、設定 しようとする国際化情報がフィルタに必ず合致するようにしてください。この フィルタでは、正しいロカールの名前だけを許可するように限定しておきます。 ユーザ・プログラム(特に setgid や setuid してあるプログラム)は、これらの 値を次の変数から取得します。それは、NLSPATH や LANGUAGE、LANG、古くなった LINGUAS、LC_ALL、その他の LC_* (LC_MESSAGES だけでなく、LC_COLLATE、 LC_CTYPE、LC_MONETARY、LC_NUMERIC、LC_TIME も)です。 Web アプリケーションでは、ユーザが要求する言語情報は Accept-Language 要求ヘッダもしくはフォームの値として提供されます(アプリケーションは、 Content-Language: ヘッダーを使って、返されるデータの実際の言語設定を 示すべきです)。 ユーザがあなたの環境変数を設定していれば(つまり、setgid や setuid して あるプログラム)、環境変数のフィルタリングの一部もしくは、入力フィルタ(たとえば CGI スクリプト用に)の一部としてこの値をチェックできます。 GNU の C ライブラリである「glibc」は、setgid や setuid したプログラムでは LANG の値を受け付けないものがありますが(特に「/」を伴ったもの)、そのフィルタ にはエラーがあることがわかっています(たとえば、Red Hat はこのエラーを修正 するために、glibc のアップデートを 2000 年 9 月 1 日にしています)。 この種のフィルタリングは規格上必要とはされていませんので、あなた自身が フィルタリングを行なうことで、より安全にできます。 フィルタリング言語の設定については、手引きが何も見つけられませんでした。 そこでここでは、この件について私自身が調査したことに基づいて、アドバイスを します。

まずは、これらの設定で何が正しい値かについて一言述べておきます。 言語設定は、一般的に IETF RFC 1766 で定義している標準タグを使っています (2 文字の国コードを基本タグとし、その後に任意でダッシュ(-)で区切ったサブ タグが続くことがあります。環境変数の場合、アンダースコアを代わりに使います)。 しかし、これは柔軟であるとは言い難く、3 文字の国コードがまもなく利用できる ようになるでしょう。 また、機能を拡張した メジャーな 2 つのフォーマットがありますが、互換性が あるとはいえません。それは X/Open フォーマットと CEN フォーマット(European Community Standard)です。 どちらも許可して良いでしょう。 典型的な値としては、「C」(C ロカール)や「EN」(英語)、「FR_fr」(フランスの 慣習が生きている地域で利用しているフランス語)があります。 また標準ではない名称を使っている場合が多く、プログラムは「別名(alias)」を 使える仕組みを開発する必要にせまられ、標準ではない名称を扱えるようになり ました(GNU の gettext なら /usr/share/locale/locale.alias、X11 なら /usr/lib/X11/locale/locale.alias を見てください。「alias」ではなく、 「aliases 」とする必要があるかもしれません)。 どちらも普通は利用できるはずです。 gettext()のようなライブラリは、これらのエイリアスをすべて受け付けなければ ならず、できるだけ適切な値を適用できなければいけません。 より詳しい情報は、FSF [1999]や li18nux.org の Web サイトにあります。 フィルタは、不必要な文字を許可すべきではありません。特に「/」(信頼されている ディレクトリから抜け出てしまえる可能性がある)や「..」(上位ディレクトリに 移動できてしまう可能性がある)は許可してはいけません。 NLSPATH に含まれる他の危険な文字には、「%」(置換を表わす)と「:」(ディレクトリ の区切り)があります。私が所有している他のマシン用資料によると、実装によって、 これらの文字が他の値を示すために使われている場合もありますので、禁止した方が 安全である、となっています。

4.7.4. 結論

つまり私としては、NLSPATH を削除するか、再設定するかのどちらかを推奨します。 そうしないと、その値を渡してくるユーザを信頼しなければいけなくなります。 HTTP における Accept-Language ヘッダ(を使うなら)や、ロカールを指定する フォームの値、上記で挙げた環境変数の LANGUAGE や LANG、古い LINGUAS、 LC_ALL、その他の LC_* に対しては、信頼できないユーザからのロカールに null (値なし)を設定するか、正規表現全体にマッチした値だけを許可するように フィルタをかけてください(私は最近このフィルタに「=」を追加しました)。
 [A-Za-z][A-Za-z0-9_,+@\-\.=]*
正しいロカールで、このパタンにマッチしないものを見たことがありませんが、 このパタンで、ロカールを利用した攻撃を防ぐようです。 もちろん、要求されたロカール中に利用できるメッセージが存在する保証はあり ません。 しかしその場合でも、これらのルーチンはデフォルトのメッセージ(通常は英語) を表示します。これがセキュリティ上問題とはならないのは確かです。

本当にこだわるなら、代わりに li18nux で提供しているロカールのパタンに マッチするものだけを使ってください。
 ^[A-Za-z]+(_[A-Za-z]+)?
 (\.[A-Z]+(\-[A-Z0-9]+)*)?
 (\@[A-Za-z0-9]+(\=[A-Za-z0-9\-]+)
  (,[A-Za-z0-9]+(\=[A-Za-z0-9\-]+))*)?$
どちらの場合も、POSIX の拡張(「新しい」)正規表現の考えに基づいています (Unix ライクなシステムなら regex(3) や regex(7)を見てください)。

もちろん言語というものは、標準的な手段で書き文字を表現できなくては、言語を サポートしているとは言えません。このことから文字のエンコードという問題に直面 することになります。