訳者: nog
原文: Mozilla Security Review and Best Practices Guide(2006年9月7日版)

Mozilla のセキュリティ評価と最良実践ガイド

草稿 3 - 2002年5月17日

キーポイント

問題

目標: 何から保護するのか。

「セキュリティ」はかなり漠然とした用語です。「プライバシー」もそうです。より明確に、Mozilla に関するコードを書くとき、何から保護する必要があるのかを以下に示します。

何から保護しないのか。

最も安全なコンピューターとは電源が切られ、プラグが抜かれて、コンクリートの中に埋められたものです。セキュリティと機能性は常に競合しているので、保護するものとしないものとしないものとを区別する必要があります。 特に:

要するに、主な目標はウェブページを見たり、メールを読んだりするという行動の中で直面する攻撃を防ぐことです。他の攻撃源もありますが、重大な関心事ではありません。

課題

Mozilla はオープンソースなので、私たちはセキュリティ問題を発見する上で明らかに有利な立場にあります。問題がないかコードに目を通すことのできる人の数に制限を設けていません。同時に、克服しなければならない明確な課題があります:

解決策

以下が安全な機能を設計し、問題を未然に防ぎ、ありふれた落とし穴を捜す方法についての Mozilla のプログラマ、レビュアーやユーザインターフェイス製作者のためのガイドラインです。

セキュリティの黄金律:どんな入力源であっても信用してはいけません

偏執病的に聞こえますが、うまくいきます。あなたの入力がどこからのものなのか考えてください。ネットワークからのデータか、ディスク上のファイルか、ユーザの入力か、環境変数か、それともあなたの関数への引数か。もし入力がひょっとしたらあなたの管理外の源に由来するのならば、それがあなたの予想する書式であることを確実にするためにそれを確かめてください。全ての離れたサーバー、環境設定ファイル、コマンドライン引数が、損害を与えることに専念している悪質なハッカーによって作成されたと想定してください。どの入力の組み合わせでもコードが思いがけない振る舞いをするようにならないことを確かめてください。この方法で自分のコードを見ることで信頼性も向上します。セキュリティと信頼性は近い親類なのです。以下の点のほとんどは本当にこの基本規則の例です: 決して確認せずに入力が安全だと想定してはいけません。

Chrome JS

chrome を書くことはウェブページを書くことと非常によく似ており、セキュリティ上の懸念を引き起こします。しかし、chrome JS はネイティブなブラウザのコードの一部として考えられており、それができることについて何の制限も無いので、chrome に対する関心はより高いです。

覚えておくべき最も重要なことは全てのユーザの入力と、(更に重要なことに)URL を含む、ウェブからのデータを信用できず潜在的に悪意のあるものとして扱うことです。ウェブからのデータが chrome 内のどこで使われようとも、潜在的に危険な要素としてフィルターにかけなければなりません。

javascript: URL では、(一般に HTML/XML の要素としてレンダリングされる)戻り値とスクリプトの実行による副作用の両方について気をつけてください。

C++

C や C++ は用途が広い一方で、セキュリティ上の誤ちを非常に犯しやすいです。特にある種の関数は非常に危険で、それらを避けるか非常に気を付けて使うべきです。この節では C や C++ のコードを書く時に避けるべき共通のセキュリティ上の落とし穴が記述されています。Mozilla のどこであってもこのような誤りはセキュリティ上の脆弱性を引き起こしうることを覚えておいてください。

バッファオーバーラン

バッファは連続したメモリーのブロックです。バッファオーバーランとはバッファが保持できるよりも多くのデータをバッファに書き込むことです。余分なデータはバッファに隣接したメモリー内の他の値を上書きします。それらの値が何であるかによって、それらを上書きすることで、攻撃者はプログラムの処理方法を変えたり、攻撃者の選んだ任意のコマンドを実行することさえできます。C や C++ はこれに対する組み込みの保護を提供していません。(JavaScript の場合は、更なるデータに対応するために必要に応じて動的に大きくなるバッファによって、提供しています。) 以下が必要最低限の例です:

    void dangerousFunction(char* input)
{
char buf[100];

PL_strcpy(buf, input);

// 更なるタスク...
}

これは非常に危険な状態です。一般的には、どこであれサイズのチェックをしていない PL_strcpy(または標準 C ライブラリ関数 strcpy)を見たら、大いに気にしなければなりません。この例では、バッファはプログラムのスタックに蓄えられます。スタック上には他のローカル変数、引数およびこの関数が終了したときにプログラムの実行がジャンプする先の戻りアドレスもあります。もし攻撃者がこの関数への入力として 100 文字以上を渡したら、PL_strcpy 関数はバッファを満たし、そして戻りアドレスを含む、スタック上の他の値を上書きするでしょう。もし攻撃者がどこにバッファに関係のある戻りアドレスがあるのかわかったならば、攻撃者は PL_strcpy に戻りアドレスを攻撃者の選んだ値を設定させるような入力を作り上げることができます。ありふれた手法は攻撃者にユーザのマシンへの更なるアクセスを与えるアセンブリコードでバッファを満たし、戻りアドレスをバッファの最初に設定するというものです。

これが最も単純な例です。バッファオーバーランはこの例よりもいくらか捕らえにくい多くの形で起こります。一つには、オーバーランはスタックだけではなくヒープでも起こり得ます。つまり、malloc や new で割り当てられたメモリー空間はその上オーバーフローし得たり、隣接するデータが改変されうるのです。バッファに隣接するデータ層を予測することはより困難なので、ヒープ上のオーバーランは不正な収穫のためにはより困難です。しかし、弱点を突く手法は依然として可能です。

たいていのバッファオーバーフロー問題への解決策はバッファへコピーできるデータ量を制限することです。上の例では、これを行なう最も簡単な方法は PL_strcpy を PL_strncpy という境界のあるバージョンで置き換えることです:

void saferFunction(char* input)
{
char buf[100];

PL_strncpy(buf, input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';

// 更なるタスク...
}

私が PL_strncpy(buf, input, 99) を使うことができたことに注意してください。しかし、もし誰かがバッファのサイズを変更するとしたら、その上 PL_strncpy の呼び出しも変更することを思い起こさなければならないでしょう。コンパイル時に計算され、パフォーマンスへの影響のない、sizeof(buf) を使用する方がずっと安全です。もう一つの注意点は私が明示的にバッファの最後のバイトに null 文字をセットしたことです。(C ライブラリー版の、strncpy と同様に)PL_strncpy はバッファを null で終端させることを保証しないのでこれは必要です。

{PL_}strcpy に加えて {PL_}strcat、sprintf 一群の呼び出し、scanf や gets のような他の関数もバッファオーバーラン問題の危険にさらされています。完全なリストは下の **リンク** にあります。

書式バグ

printf()、fprintf()、sprintf() や snprintf() のような関数は書式関数として知られています。これらの関数は '%s' のような書式指定子を含むことのできる、書式文字列を引数に取ります。これらの記号に出会うと、関数を書式文字列にしたがって引数に基づき結果として生じる文字列へデータを挿入します。例えば、

    printf("Today is the %ith day of %s", 5, "May");    

は文字列 "Today is the 5th day of May" をコンソールに出力します。書式関数の危険性は攻撃者が書式文字列の内容に影響を及ぼすことができるときにやってきます。これはもし書式文字列が更なる '%' 書式指定子を含むならば、関数には余分な引数があることになり、関数はスタックから関数の引数やローカル変数を読み込み、それらを出力の文字列に含み始めるからです。このことによって攻撃者はあなたの関数の内部状態について情報を読み取ることができるかもしれません。それより悪いことに、'%n' 書式指定子は対応する引数に、出力した文字列に書き込まれたバイト数を書き込み、もし引数よりもパーセント書式指定子が多いならば、スタック上の他の場所に書き込みます。例えば戻りアドレスの上書きなどにより、攻撃者が実行中のプログラムを変えたり任意のコードを実行させることさえもできるという点で、バッファオーバーランに非常によく似た状態をこれによって作り出します。

** 例 **

これらの問題への解決策は信頼できないウェブコンテンツ(または、更に言えばユーザ)に書式文字列を指定させないということです。理想的には、書式文字列はハードコードするべきです。単純な例を挙げると、次のような printf の呼び出しは使用せず:

    printf(str);    

必ず以下のようにしてください:

    printf("%s", str);    

この方法で書式文字列に「錠をおろす」ことで脆弱性が排除されます。もし書式文字列をハードコードできず、それを含むデータが信用できない入力源に由来するかもしれないのならば、あらかじめフィルターにかけてください。例えば、もし書式文字列が %s 書式指定子を使用して、たった三つだけの異なる文字列を含むと予想するならば、そのように入力を検証できるかもしれません:

    void buildString(char* formatIn, char* data1, char* data2, char* data3)
{
PRInt32 percentCount = 0;
for (PRInt32 j = 0; j < PR_strlen(formatIn); j++)
{
if (formatIn[j] == '%')
{
percentCount++; // % 書式指定子をインクリメントします
 
if (formatIn[j+1] != 's')
// %s 以外の書式指定子を見つけたので、
//エラーで強制終了します
return NS_ERROR_FAILURE;

if (percentCount > 3)
// 3を超える % 書式指定子があるので、エラーで強制終了します
return NS_ERROR_FAILURE;
}
}

char buf[1000];
snprintf(buf, sizeof(buf) -1, formatIn, data1, data2, data3);
buf[sizeof(buf)-1] = '/0'
}

snprintf を呼び出す前に、書式指定文字列が %- 書式指定子を 3 つだけ含み、更に %s だけを含むことを確かめるために調べています。バッファオーバーランを避けるために sprintf の代わりに sizeof() の制限と共に snprintf を使い、明示的に バッファを null で終端させていることに注意してください。

危険な関数

危険な関数
名前 危険水準 問題 解決策
名前 危険水準 問題 解決策
gets 非常に高い 境界チェックなし gets を使わないでください。代わりに fgets を使ってください。
strcpy 非常に高い 境界チェックなし strcpy は元の文字列が一定で、コピー先の文字列がそれを保持できるほど十分大きい場合に限り安全です。もしそうでなければ、strncpy を使ってください。
sprintf 非常に高い 境界チェックなし、書式文字列攻撃 sprintf を安全に使うのは非常に難しいです。代わりに snprintf を使ってください。
scanf, sscanf 高い 境界チェックがない可能性、書式文字列攻撃 全ての %- 書式指定子が対応する引数の型と一致していることを確かめてください。境界チェックなしで '%s' 書式指定子を使わないでください。x が対応する引数のバッファのサイズである場合、'%xs' を使ってください。信用できず、検証されていないデータを書式文字列内で使用しないでください。
strcat 高い 境界チェックなし もし入力のサイズがよく分かっておらず固定されていなければ、代わりに strncat を使ってください。
printf, fprintf, snprintf, vfprintf, vsprintf, syslog 高い 書式文字列攻撃 信用できず、検証されていないデータを書式文字列内で使用しないでください。もし書式文字列がウェブコンテンツやユーザの入力から影響されうるならば、これらの関数を呼び出す前に書式文字列を適切な数字と %- 書式指定子の型で検証してください。出力先のサイズ引数が正しいことを確かめてください。
strncpy, fgets, strncat 低い null 終端ではないかもしれません いつも明示的に目的のバッファを null で終端させてください。サイズ引数が正しいか確かめてください。目的のバッファに null 文字を加えるように注意してください!

ファイルアクセス問題

競合
一時ファイル
パーミッション
シンボリック リンク攻撃