Security
セキュリティ

注意事項

毎回の授業の最初に出席メールを提出せよ.

前回までの出席記録課題と提出状況 を確認せよ.

12 章 セキュリティ

PHP とは不特定多数のユーザにより,ウェブの仕組みを利用してサーバ上で プログラムを動作させることを目的としている. 悪意ある ユーザに危険なプログラムを実行させないように プログラマの責任で適切なプログラムを書かなければならない.

典型的な例は p.312 の下から 5 行目にあるものである. ユーザによって $username の値が作成された場合,そのまま信用して 値を使うと表示されてはいけないものが表示されてしまう可能性がある.

12.1 入力のフィルタリング

データが「汚染」されている,とはユーザによって作成されたデータであることを言う. 悪意のある危険なデータが仕込まれている可能性のあるデータという意味.

ユーザ入力によるデータの危険性を具体例で挙げると, 入力データとしてユーザがエスケープ文字などの特殊文字を駆使したデータを埋め込み, サーバのパスワードファイルなど,本来ユーザに与えてはいけない情報を表示させる ような攻撃がある.CGI が普及し始めたころに頻繁に起こった.

汚染されたデータは使用する前にこちらが想定しているような安全なデータだけを 通過させるフィルタリングを行う必要がある.フィルタリングが甘いと当然危険性は 残る.良いフィルタリングを行うためには以下の原則に従うと良い. 特に最初の項目は重要である.

  1. ホワイトリスト方式を使用すること
  2. 無効なデータを修正しないこと
  3. フィルタリング済みデータを区別できるようにしておくこと

p.306 15 行目からのフォームは,送信ボタンが押されると パラメータ color の値が red, green, blue のいずれかの POST メソッドを process.php に対して実行する. 一見 process.php は必ず上記の値のどれかで呼び出されるように 思ってしまうが,プログラム等から任意の値で POST メソッドを 実行することができる. GET メソッドであった場合を考えるほうが想像しやすい. GET の場合はブラウザのアドレス欄に手で書き加えるだけで任意の値を設定できる.

フォームの select 要素で選べる値を制限していても,動作プログラムである process.php のほうではどんな値が渡されるか分らない(汚染されている)という 前提でデータをフィルタリングする必要がある.

p. 306 下から 10 行目から始まる例では,上記 3 原則に従っている.

  1. red, green, blue 以外の値を無効としている
  2. 無効なデータを修正せず,処理を終了させている
  3. 連想配列 clean にはフィルタリング済みの パラメータのみが格納される.

この処理の後,配列 clean に格納されているパラメータだけを 使用するようにプログラミングすればよい.

少数の選択項目ではなく,ユーザ名文字列のような任意文字列の場合は 英数字のみの系列を有効とする,などの手法を使う.正規表現を使うと ある程度複雑な条件のチェックができる.

p. 308 の /[^A-Za-z \'\-]/ という正規表現について説明する. [] は中にならべられた文字のうちいずれか一文字を意味する. [A-Z] は [ABCDEFGHIJKLMNOPQRSTUVWXYZ] の短縮形で,A,B,...,Z のいずれか一文字 を意味する.同様に [a-z] は a,b,...,z のいずれか一文字を示す. それに空白記号と,「\」でエスケープされた 単一引用符と,マイナス記号が並べられている. [A-Za-z \'\-] と全て並べて書くことで 「大文字小文字のアルファベット,空白,単一引用符,マイナス記号の 55 種類の文字のうちの一文字」 を意味する.「^」は否定の意味であるから [^A-Za-z \'\-] で「大文字小文字のアルファベット,空白,単一引用符,マイナス記号の 以外の一文字」を意味する.つまり使ってはいけない文字 1 文字を意味する. これとマッチングを行った結果が真であれば使ってはいけない文字が使用されている と判断できる.

SQL のクエリ文字列としてユーザが故意に作成した特殊文字を含む危険な 文字列を処理してしまう問題を扱う.

p.309 の最初の例にはいくつか前提がある.

  1. テーブル users には username にユーザ名,password に 特定のハッシュ関数にパスワード系列を入力したときの出力 (ハッシュ値)値が保存されている.
  2. ログイン時,ユーザが入力したユーザ名,パスワードに対して パスワードに同じハッシュ関数をかけた値を計算する. 正しいユーザであれば同じユーザ名とパスワードのハッシュ値が テーブルにあるはずであり,なければ正しいユーザではないと 判断する.

ユーザのパスワードそのものをデータベースに保存しなくてもよいため 安全であるという理由から Unix システムなどで一般的に使われる手法である. p.309 最初の例は上記 2. の処理に用いることを意図している.

ユーザは text 型 input 要素など,自由記述型の入力 フォームから,パラメータ users の値としてユーザ名,パラメータ password の値としてパスワードを入力し,POST メソッドでこのスクリプトにアクセスする.

3 行目の count はテーブルのアイテム数を返す関数である. この例のクエリ文字列 $sql は,users という"ユーザ名とパスワードのハッシュ値" を格納したテーブルに対して username が パラメータ username の値と一致し,かつ password が パラメータ password の値のハッシュ値と一致するもの の個数を返すものである.正規ユーザであれば count は 1 を返し, そうでなければ 0 を返すはずなので, 「このクエリの戻り値が正のときにログイン成功」としてこのスクリプトを使うことを想定 してその脆弱性を説明している.

問題点はユーザが入力した(汚染された)パラメータ username の値の 文字列をそのまま使用していることである.ここに chris'-- と記入されると パスワードを知らなくても chris としてログインできてしまう.

これを防ぐにはユーザが入力したパラメータの内容から,特殊な意味を持つ文字 を別の文字に置き換えるエスケープ処理を行うことがあげられる. また,データベースによっては単純なデータとして扱うことが保証できるように指定する 機能があるものもある.これを利用するとエスケープ処理に頼らなくても安全にデータベースを 操作することができる.

12.2 出力のエスケープ

ネットショッピング,掲示板のプログラムなど,ユーザがブラウザから処理内容を入力し, それによりサーバ内部のデータベースが更新される形式は多く利用されており, PHP の代表的な使われ方である.この場合,ユーザの入力から結果を得るまでの全体の 処理は以下の流れになっている.

  1. ユーザがブラウザに処理内容を入力する.
  2. ブラウザがサーバのスクリプトにアクセスし,パラメータを送る.
  3. PHP が HTTP で送られる文字列を処理して データベースのクエリ文字列を作成する.
  4. データベースへのクエリを実行し,結果を得る.
  5. データベースからのクエリ結果を 処理して表示用の HTML ソースコードを作成する.
  6. ソースコードをブラウザに送る.
  7. ユーザはブラウザで表示結果を見る.

HTML とデータベースのクエリ文字列でそれぞれ独自の特殊文字があるため, エスケープの規則が違う. HTML で値を受け取った後(上記3. の最初)は HTML 用のエスケープの解除, データベースへ送るへクエリ文字列を作成する時(3. の最後)はデータベース用のエスケープ処理, クエリ結果を受け取ったあと(5. の最初)はデータベース用のエスケープ解除, HTML への送信時(5. の最後)は HTML 用のエスケープ処理が必要になる.

関数 htmlentities() は文字列を HTML 中でも安全に使えるよう, HTML で特殊な意味をもつ文字を規則に従って書き換えるものである.

p.311, 16行目の例が HTML 中にあると,まず HTML として特殊文字が 解釈され,リンク先の URL の部分は URL としての特殊文字の処理が行われる.

関数 urlencode() は文字列を URL として使えるよう, URL で特殊な意味をもつ文字を規則に従って書き換えるものである.

上の例のようにHTMLとしても URL としても処理される文字列は urlencode() と htmlentities() の両方で処理しなければならない.

mysql 用のエスケープ関数として mysql_real_escape_string() がある.

p.312, 12 行目の例では PEAR::DB の機能でエスケープ処理を行う.

一般的な Unix システムでは /etc/passwd にユーザ名と暗号化された パスワードが格納されている.p.312 最後の例は,ディレクトリを一段昇る 「..」を利用した相対パスでディレクトリの階層を一旦上り, 閲覧が許されていないファイルを表示させる古典的な攻撃である.

ファイル名にユーザが入力したパラメータを使うのは危険なのでやってはいけない.

p.313 最後の例は realpath を適用したあと basename を適用することで ファイル名だけを取り出すことでチェックを行っている.

12.3 クロスサイトスクリプティング

p.314, 15 行目はユーザが自由に入力できる username パラメータの値 をそのまま出力している.このため,例えば <script> 要素を入力して ユーザが好きなスクリプトを動作させることなどが可能になる.

これを防ぐためには特殊な意味を持つ文字をエスケープ処理で置き換え, 適切なフィルタリングを行うことが重要である.

12.4 セッションの固定化

ユーザに特定のセッション ID を使用させることで,その ID を使って なりすましを行う攻撃である.ログイン後に session_regenerate_id() でセッション ID を再生成するようにすれば防ぐことができる.

12.5 ファイルのアップロード

ユーザが指定するファイル名を信用しない,アップロードするファイルサイズに 上限を設ける,move_uploaded_file() を使う,などが重要である.

12.6 ファイルへのアクセス

ユーザが「..」などを使ってディレクトリを上に上って権限の無いファイル にアクセスされることに対する対処として, php サーバの設定ファイル中で open_basedir を指定することで ユーザがアクセスできるファイルと制限する方法がある.

自分にしか読めないファイルを作成するときに,ファイルを作成してから パーミッションを設定してはならない.ファイル作成の前に umask で パーミッションを指定してからファイルを作成する.

同じマシンで動いているスクリプトは同じユーザ名のプロセスとして動作している. そのため,扱っているファイルは同じ権限でアクセスできるため, ファイル名さえわかれば別のスクリプトがアクセスしているファイルにアクセスできる.

機密データをファイルに保存しないようにする.

セッション情報ファイルの置き場所が決まっている場合,他のユーザにセッションを読み書き される可能性がある.

ドキュメントルート以下のファイルは,ファイル名さえわかればアクセスされる 可能性がある.ライブラリはデータベースの配置場所をそれ以外に設定する.

12.7 PHP のコード

eval() は任意の PHP コードを実行する.ユーザが作成したデータを eval() に渡すのは非常に危険である.

12.8 シェルのコマンド

ユーザが作成したデータをシェルに実行させる(Unix システムに実行させる) ことも基本的に非常に危険である.


Updated in July 21, 2009, index.html, Yamamoto Hiroshi