8.4. データフォーマットを制御する(「書式文字列」)

コンピュータ言語における出力ルーチンは、書式を制御するパラメタを持っている ケースが多くあります。 C で最もよく知られた例が printf()系のルーチン(printf()や sprintf()、 snprintf()、fprintf()等)です。 C での他の例は syslog()(システムのログ情報を書く)や setproctitle() (プロセス識別子の情報を文字で表示するのに使用)です。 関数に付いた名前は、「err」や「warn」ではじまったり、「log」が含まれていたり、 「printf」で終わったりしている点に注意する価値はあります。 Python は「%」演算子を持っていて、文字列にあると書式を同じように制御 します。 プログラムやライブラリの多くは、フォーマット機能を定義していて、普通は 組み込みルーチンを呼び出し、さらに処理を加えます(たとえば、glib の g_snprintf()ルーチンのように)。

意外にも、これらのフォーマット機能を見くびっているように思える方々がたくさん いて、信頼できないユーザからのデータを、書式パラメタとして利用しています。 フィルタをかけることなしには、信頼できないユーザからのデータを書式パラメタ として使用しないでください。 例としてぴったりなのが下記です。
  /* Wrong ways: */
  printf(string_from_untrusted_user);
  /* Right ways: */
  printf("%s", string_from_untrusted_user); /* safe */
  fputs(string_from_untrusted_user); /* better for simple strings */

さもなければ、攻撃者は書式文字列を慎重に選んで、ありとあらゆる悪さを行います。 C の printf() が良い例です。 printf()を使って、ユーザが制御している書式文字列を悪用する方法はたくさん あります。 これらの方法には、長い書式文字列によるバッファオーバーラン(攻撃者がプログラム を完全にコントロールできてしまいます)や認められていないパラメタを使用する 変換規則(予想外のデータを挿入する)、まったく予想できない結果になる値を作り だすフォーマット等があります(不適切なデータを前後に入れて、後で利用する 時に問題を起こす)。 特にひどいケースは、printf の %n の変換規則です。この規則では、多量の文字 をポインタ引数にあるだけ書き込みます。 これを使うと、攻撃者は書き込もうとした値に上書きできてしまいます。 また、攻撃者はほとんど任意の場所に上書きさえ可能です。それは本来は渡され なかった「パラメタ」を指定できるからです。 これらの攻撃について詳しく論じた資料はたくさんあります。たとえば、 Avoiding security holes when developing an application - Part 4: format strings がその 1 つです。

結果をユーザに返すケースが多いので、この攻撃はスタックについての内部情報 をさらす場合にも使われます。 この情報は、StackGuard のようなスタックを守るシステムを回避するのにも利用 できます。StackGuard は定数の「canary」値を用いて攻撃を検知します。 しかしスタックの内容が表示できるなら、現在の canary の値がさらされて、攻撃 を受けやすくなります。

書式文字列は、ほとんどいつも文字列定数のはずですが、国際化を見つけ出す関数 呼び出し(たとえば、gettext の _())を含むこともあります。 この検索を行うには、プログラムが制御する値に制約を設けなければなりません。 つまりユーザは、プログラムが管理しているメッセージファイルからしか選択 できないようにしなければいけません。 ユーザのデータには、使用前にフィルタをかけられます(書式文字列として正しい文字、 たとえば [A-Za-z0-9] のような文字をリストアップしてフィルタを設計します)。 しかし、問題を防ぐのに、普通はもっと簡単で良い方法があります。それは、書式 文字列を固定するか、fputs()を使用するかです。 先に「出力」の問題としてリストアップしましたが、内部的に出力以前のプログラム に対しても問題が発生します(出力ルーチンはファイルに保存しているかもしれず、 snprintf()を使って、内的な状態の生成さえしているかもしれないからです)。

入力フォーマットの問題によって、セキュリティ上の問題が発生するのは、あながち ないともいえません。CERT Advisory CA-2000-13 を見れば、この弱点を利用した 攻撃の例が載っています。 これらの問題がいかにやられやすいのかについて、さらに詳しい情報は、 Pascal Bouchareine 氏の 電子メールで、タイトルが「[Paper] Format bugs」 にあります。これが書かれたのは 2000 年 7 月 18 日です Bugtraq。 2000 年 12 月現在、gcc コンパイラの開発版では、安全でない書式文字列に対して の警告メッセージをサポートしています。開発者がこれらの問題を回避できる ように支援する試みです。

もちろんこれは国際化の検索が実際のところ安全かどうか、という疑問をはぐら かしています。 自分で国際化の検索ルーチンを作成しているなら、信頼できないユーザには、正しい ロカールだけが指定可能で、勝手なパスを指定するような、何か違うものを指定 できないようにしてください。

信頼できる相手に対するものであっても、国際化によって作られた文字列には 制限をかけなければいけません。これは明白です。 そうしないと、攻撃者はこの機能を利用して書式文字列の弱点に突け込みます。 これは C や C++ のプログラムに顕著です。 この点は Bugtraq で話題になってきました(たとえば、John Levon 氏が 2000 年 7 月 26 日に Bugtraq に投稿したものを見てください)。 さらなる情報は、ユーザに許可する選択は、正しい言語の値だけにすることを論じた Section 4.7.3 を見てください。

プログラミング上のバグであるとはいえ、いろいろな国々でさまざまな方法で数字を 表記している点に触れるのは意味があります。特にピリオド(.)とカンマ(,)が、 整数と端数部分を区切るのに使われている点に対して。データを保存したり、 読み込んだりするなら、使用中のロカールがデータの取り扱いに絶対干渉しない ようにしなければなりません。 そうしなければ、フランス語ユーザは英語ユーザとデータを交換できないかもしれ ません。理由は、保存されたり、取り出したりしたデータは違う区切りを使っている かもしれないからです。 セキュリティ上の問題としてこれが利用されるのかどうかは定かではありませんが、 ないとも思えません。