トップ  > メモ一覧  > カテゴリ「セキュリティ」の絞り込み結果 : 1件

1件中 1 〜 1 表示  1 

No.3456 OpenPNE3セキュアコーディングガイドライン

OpenPNE3 セキュアコーディングガイドライン

概要

このドキュメントは、セキュリティを前提としたコーディングをおこなうために従うべきガイドラインを、 OpenPNE3 に貢献する個人またはチームに示すためのものです。

ユーザをなりすましから守る

パスワードの再確認

メールアドレス変更、パスワード変更、携帯電話個体識別番号変更といったような重要な設定変更をおこなう場合には、必ずパスワードを要求するようにしてください。

セッション管理の不備や、後述する XSS (Cross Site Scripting) などによってなりすましログインがおこなわれてしまった場合でも、重要な操作をおこなうまえにパスワードを要求すれば、パスワードが知られない限りはその 操作をおこなうことができないわけですから、被害をある程度は抑えることができます。また、パスワードという秘密情報を含めることにより、結果的に、後述 する CSRF (Cross Site Request Forgeries) への対策にもなります。

正しく入力値を検証する

サーバサイドでの入力値検証

入力値検証には、必要に応じてフォームフレームワークの利用や、フォームフレームワークで使われる sfValidator* 系のクラス (もしくは OpenPNE 側でそれらのクラスを継承しているもの) などを利用するようにしてください。場合によっては日時や数値など、アプリケーションが期待する変数型や文字列の形式への変換も実施してください。

ただし、たとえば入力値検証において、後述する XSS (Cross Site Scripting) や SQL Injection といった脆弱性への対応を目的として、文字列を前もってエスケープしてしまうといったことがないようにしてください。これは典型的な「誤ったセキュリティ 対策」と呼ばれるものです。文字列をどのようにエスケープするべきかは、その文字列がどの文脈で用いられるかによって変化します。それを入力の段階から明 確にすることは難しいですし、当初の実装時点ではその入力値が特定の文脈でしか使われていなかったとしても、今後変更されるかもしれません。

ホワイトリストやブラックリストの利用

ホワイトリストを使用した入力値検証については可能な限り積極的におこなうようにしてください。ホワイトリストにより入力値をプログラムが期待するもののみに限定できます。

これは、特に、ユーザ入力値をエスケープすることによる脆弱性の回避などが困難なケースなどで有効です。

ブラックリストも有用ですが、漏れが生じる危険がありうることを考えると、そのブラックリストはセキュリティ対策としては満足なものでないかもしれ ません。ホワイトリストのほうがブラックリストよりも確実であるということは認識してください。もし少量の変更でブラックリストからホワイトリストに置き 換えることができる場合は置き換えを検討するべきです。

Null Byte Attack による制限の回避への対策

PHP には、「バイナリセーフである関数」「バイナリセーフでない関数」が混在しています。このうち、「バイナリセーフでない関数」を使用して入力値に対するバ リデーションをおこなったつもりでも、 Null Byte Attack として知られる攻撃によって突破されてしまう可能性があります。

ヌル文字は、 C 言語では文字列の終端として認識される文字です。 PHP は C 言語で記述されているため、 PHP 内部の関数などがヌル文字の含まれた文字列をそのまま C 言語の文字列として処理してしまうと、ヌル文字の登場した箇所を文字列の終端としてしまうために、意図した動作をしなくなる可能性があります。ヌル文字を 文字列の終端としてしまう、バイナリデータが含まれる文字列を考慮していない関数を「バイナリセーフでない関数」と呼びます。逆に、ヌル文字を文字列の終 端とはみなさない、バイナリデータが含まれる文字列を考慮した関数を「バイナリセーフである関数」と呼びます。

「バイナリセーフである関数」を用いて入力値をチェックしたのちに「バイナリセーフでない関数」を使用して処理をおこなうか、「バイナリセーフでな い関数」を用いて入力値をチェックしたのちに「バイナリセーフである関数」を使用して処理をおこなった場合、チェックした文字列と実際に処理する文字列が 異なることになるわけですから、意図しない動作になる可能性が出てきます。

Null Byte Attack を利用して制限を回避し、脆弱性が成立した例として、以下のようなものがあります。

phpBB "avatar_path" PHP Code Execution Vulnerability:
http://secunia.com/advisories/22188/

OpenPNE では、例外的に、リクエスト中のヌル文字のみ除外して opWebRequest クラスのインスタンスに渡しています。そのため、 opWebRequest を経由してリクエストパラメータを取得すると、ヌル文字が除去された文字列が得られることになります。これは、 PHP でバイナリセーフである関数とバイナリケースでない関数がすべて明確に示されているわけではなく、常にその関数がバイナリセーフかどうかを確認しながら関 数を使用するのは困難なことと、 OpenPNE が Web アプリケーションであり、ヌル文字を意図的に受け入れるべき機会は少ないことなどが理由です。

ただし OpenPNE は $_POST, $_GET, $_REQUEST, $_COOKIE などのスーパーグローバル変数を直接変更することはしないようにしているため、 opWebRequest 経由ではなく直接スーパーグローバル変数にアクセスすることで、ヌル文字が除去されていない文字列を取得することができます。意図してヌル文字を含んだ文 字列を取得したい場合は、スーパーグローバル変数に直接アクセスしてください。一方で、ヌル文字を含んだ文字列を取得する必要がなく、その文字列をバイナ リセーフでない関数の引数として扱ってしまう可能性がある場合は、 opWebRequest 経由でのアクセスに切り替えるか、自分でヌル文字を取り除くように変更するようにしてください。

クライアントサイドでの入力値検証

JavaScript などを使用したクライアントサイドでの入力値検証は、セキュリティ対策としては意味をなしません。それは JavaScript などを無効にしたり、 Web サーバに対して直接リクエストを投げることで回避が可能だからです。

クライアントサイドでの入力値検証は、ユーザの利便性を高める目的だけにとどめ、決して頼り切ってはいけません。サーバ側ではクライアントサイドでおこなっているものと同等以上の入力値検証をかならず実施してください。

安全に HTML レスポンスを生成する

XSS (Cross Site Scripting) 脆弱性

XSS とは、攻撃者がウェブページに任意のコード (多くの場合は JavaScript) を挿入することのできる脆弱性です。

挿入されたコードは被害者のブラウザ上で実行されます。そのため、そのサイトにおいてクライアントがおこなうことのできる操作のほとんどを実行させることができます。

この脆弱性は、ユーザ入力値などの信頼できないデータを含む Web ページを動的に生成する際に、たとえば、その入力値が直接 HTML の構造に作用してしまう形で埋め込んだ場合などに発生します。

たとえば、以下のサンプルコードでは、 GET パラメータの name の値を HTML の一部として出力しています:

<?php echo '<p>Hello, '.$_GET['name'].'!</p>';

このソースコードは、 http://example.com/?name=Ebihara のようにアクセスした場合に、以下のように出力されることを意図したものです:

<p>Hello, Ebihara!</p>

しかし、 http://example.com/?name=%3Cstrong%3EEbihara%3C/strong%3E のようにして、入力値に HTML タグを含めてしまうと、この HTML がそのまま出力に挿入されてしまいます。これは、任意のスクリプトを挿入可能な状態にあるということを意味します:

<p>Hello, <strong>Ebihara</strong>!</p>

HTML タグをそのまま反映させることを意図しているのでない限り、本来は以下のように出力されなければなりません:

<p>Hello, &lt;strong&gt;Ebihara&lt;/strong&gt;!</p>

この例のように HTML にユーザ入力値を埋め込む場合の XSS への対策方法はよく知られていますが、動的に生成する JavaScript や画像、 Flash などの Web ブラウザが実行可能なコンテンツすべてについても、この脆弱性への対策を施す必要があります。

XSS による脅威

JavaScript などによってユーザのブラウザが実行可能なほとんどの操作をおこなうことができます。

もし XSS に脆弱であれば、マルウェアの配布サイトにユーザを連れて行ったり、ページ上に表示されている機密情報を流出させたりといったことができます。セッションクッキーを盗むこともできるので、攻撃者はユーザになりすましてログインすることもできます。

また、フィッシングの手口と組み合わせることで、攻撃者にユーザのパスワードを知られてしまう危険性が向上します。

非常に緊急度の高い脆弱性ですので、発覚してしまった場合は即座に対策を施すべきです。

HTML の生成

HTML の利用を制限したい入力値にある HTML 特殊文字 (&, <, >, ", ') を、出力時にエスケープする必要があります。

特殊文字が文字参照になるように適切にエスケープが施されていれば、特殊文字を利用して HTML の要素の内容に埋め込まれた入力値から HTML の構造を変更させることで XSS 攻撃を成立させることはできなくなります。

symfony のアクションを通じてテンプレートに渡された値は、明示的に無効にしていない限り、この文字参照へのエスケープの処理が自動的におこなわれます。

たとえば、以下のようなアクションを考えます:

<?php

class exampleActions extends sfActions
{
  public function executeIndex(sfWebRequest $request)
  {
    $this->name = $request['name'];
  }
}

アクションの $name プロパティに値を代入したことで、この $name の値をテンプレートから参照できるようになりました。

このときのリクエストパラメータ name の値が <strong>Ebihara</strong> だったとして、以下のようにしてテンプレートから出力しても、結果は正しくエスケープされた状態になります:

<p>Hello, <?php echo $name ?>!</p>
/* Output: <p>Hello, &lt;strong&gt;Ebihara&lt;/strong&gt;!</p> */

実はテンプレートからアクセスできる $name の値は、エスケープ済みの文字列というわけではありません。 symfony のアクションを介してテンプレートに変数をアサインすると、その変数の値は sfOutputEscaper でラッピングされます。ですので、アクションからテンプレートに渡された変数は、特別に許可された一部のクラスインスタンスを除き、実際には sfOutputEscaper およびその派生クラスのインスタンスになります。 sfOutputEscaper についての詳細は symfony の http://www.symfony-project.org/gentle-introduction/1_4/en/07-Inside-the-View-Layer#chapter_07_output_escaping を参照してください。

sfOutputEscaper のインスタンスは、アクションから渡された生の値を保持しており、 echo や . 演算子、関数などにより文字列として扱われると、保持している生の値をエスケープして返します。

これにより変数内の HTML 特殊文字のエスケープは適切におこなわれるようになりましたが、 HTML 属性値としてユーザ入力値を出力しようとする際に脆弱になることがあります:

<p id=<?php echo $name ?>>Hello, <?php echo $name ?>!</p>

このとき $name の生の文字列が Ebihara onmouseover=alert(0); だった場合、以下のように p 要素の属性値が追加されてしまい、マウスカーソルを合わせるとスクリプトが実行されてしまいます:

<p id=Ebihara onmouseover=alert(0);>Hello, Ebihara onmouseover=alert(0);!</p>

" や ' は sfOutputEscaper によってエスケープされるので、このようなケースでは、以下のように属性値を引用符で囲うことで、属性値を超えて入力値が反映されることはなくなります:

<p id="Ebihara onmouseover=alert(0);">Hello, Ebihara onmouseover=alert(0);!</p>

引用符は ' でも構いませんが、 PHP において HTML 特殊文字のエスケープに用いられる htmlspecialchars() 関数は、第二引数に ENT_QUOTES を与えない限り ' をエスケープしないため、 ' がエスケープされていない状態の入力値が ' で囲まれた属性値として埋め込まれた場合に脆弱になります。 OpenPNE のデフォルト設定では sfOutputEscaper は ENT_QUOTES つきで htmlspecialchars() をコールしますが、原則として引用符には " を使用するべきです。

ただし、この対策をしても以下のような場合は依然として脆弱なことがあるので注意してください (対策方法は後述します)。

  • イベントハンドラを記述するような属性値 (onclick や onmouseover など) に入力値を埋め込む場合 (JavaScript を記述可能)
  • 任意の要素の style 属性値 (CSS を記述可能)
  • a 要素の href 属性値に入力値を埋め込む場合 (javascript: スキームで任意の JavaScript を記述可能)
  • img 要素の src 属性値に入力値を埋め込む場合 (javascript: スキームで任意の JavaScript を記述可能)

JavaScript の生成

JavaScript に動的な値を埋め込む場合、 \ を付加することによって特定の文字をエスケープをすることがあります。

しかしながら、すべての Web ブラウザで安全なスクリプトを構築するためにはどのような文字をエスケープするべきなのかが明確ではありませんし、攻撃者はエスケープされそうな文字に対 してさらに \ を付加することで、この対策を回避しようとすることがあります。そのため、エスケープに漏れが生じる可能性があります。

たとえば、 script 要素中に JavaScript を記述する場合、 </ が <\/ となるようにエスケープしなければなりません。ブラウザは </script> 等が出現した場所までを script 要素の内容とみなすためです。

以下の場合、 $_GET['example'] に対して、 symfony で用意されている esc_js_no_entities() ヘルパー関数を用いて、 JavaScript としてのエスケープをおこなっていますが、 esc_js_no_entities() ヘルパー関数は </ を適切にエスケープしないために、リクエストパラメータの値が </script><script>alert(/XSS/.source); // というようになっている場合、任意のスクリプトが実行できてしまいます。 Web ブラウザが最初に HTML をパースする際には </script> が JavaScript の文字列中かどうかというようなことは考慮しないためです:

<script type="text/javascript"><![CDATA[
var example = "<?php echo esc_js_no_entities($_GET['example']); ?>";
//]]></script>

そのため、 JavaScript に動的な値を文字列として埋め込む場合は、正確に対処することが難しい \ によるエスケープではなく、以下のどちらかの手段を用いることを強く推奨します。

  1. 英数字以外の文字を \xHH のように置換する。
  2. HTML 要素の属性値や内容として動的な値を挿入し、 JavaScript から DOM を用いてその値を純粋な JavaScript の文字列として取ってくる。

特に、 2. の方法を用いることを推奨します。以下に例を示します:

<input id="example" type="hidden" value="<?php echo $name ?>" />

<script type="text/javascript"><![CDATA[
alert(document.getElementById("example").value);
//]]></script>

この方法であれば、 HTML の作法に基づいて動的に生成した値を埋め込み、 JavaScript からそれを文字列として取得するだけで済むので、動的に埋め込まれた値は常に JavaScript の文字列のまま保たれることになり、危険は生じえません。

CSS の生成

CSS には expression() プロパティなどによりスクリプトを埋め込むことができます。ですので、 CSS に入力値を埋め込む場合も適切に配慮をおこなわなければなりません。

CSS でも \ による特定の文字のエスケープがおこなわれることがありますが、 JavaScript の場合と同様、避けるべきです。

英数字以外の文字を \xHH のように置換することで CSS に動的な値を、確実に文字列として利用できるようになります。

しかしながら、管理画面からの入力を除いて、入力値に基づいて CSS を生成することはなるべく回避することをお勧めします。

画像の生成

Internet Explorer では、レスポンスヘッダ内の Content-Type のみならず、コンテンツの中身も確認した上で、最終的にそのレスポンス内容をどのような種類のコンテンツとして処理するべきか決定します。

たとえば、 Content-Type が image/gif であっても、レスポンスの内容が HTML であれば text/html として読み込んでしまいます。 (CAPEC-209: Cross-Site Scripting Using MIME Type Mismatch http://capec.mitre.org/data/definitions/209.html)

HTML として読み込まれた画像に JavaScript が埋め込まれていれば、ブラウザは当然にその JavaScript を実行してしまい、 XSS 脆弱性が成立してしまいます。

OpenPNE ではユーザのアップロードした画像を表示するために sfImageHandlerPlugin を用意しています。このプラグインで処理された画像は、一度 GD を通して画像を生成し直した上で表示されることになるため、画像以外の情報は除去された状態になり、安全に画像を表示することができます。

ユーザの画像アップロードを許す場合、その画像をそのまま表示するということはせずに、 sfImageHandlerPlugin もしくは他の手段を用いてから表示するようにしてください。

安全に SQL を生成する

HTML の生成と同様、 SQL の生成にあたっても、ユーザ入力値など信頼できない値の取り扱いには注意が必要です。

ユーザ入力値を含んだ SQL 文を動的に生成する場合、その入力値によって、最終的に実行される SQL の構文を意図したものと違うものに変更されてしまう可能性があります。

これは SQL Injection と呼ばれている脆弱性です。この脆弱性が存在していると、攻撃者にデータベースに存在する情報の漏洩や改ざんを許してしまいます。

たとえば、以下のようなコードは SQL Injection に対して脆弱です:

<?php
// $pdo は PDO のインスタンス
$pdo->query(sprintf('SELECT * FROM user WHERE username = "%s" AND password = "%s";', $_GET['username'], $_GET['password']));

http://example.com/?username=jsmith&password=example のような URL にアクセスがあった場合、このコードの意図通りに、以下の SQL 文が生成され、実行されます:

SELECT * FROM user WHERE username = "jsmith" AND password = "example";

しかし、 http://example.com/?username=jsmith%22;%20--%20&password=whatever のような URL にアクセスすると、以下のクエリが実行されてしまいます (-- 以降はコメント)

SELECT * FROM user WHERE username = "jsmith"; -- " AND password = "whatever";

また、複数文の発行が許可されている場合には、 http://example.com/?username=%22;%20DELETE%20FROM%20user;%20SELECT%20username%20AS%20dummy%20FROM%20user%20WHERE%20%22%22%20%3D%20%22&password=whatever のような URL にアクセスされると、以下のように DELETE 文が発行されてしまいます:

SELECT * FROM user WHERE username = "";
DELETE FROM user;
SELECT username AS dummy FROM user WHERE "" = "" AND password = "whatever";

OpenPNE で SQL Injection に対処するには、バインド機構を使用して SQL 文を生成するようにするのが一番よい解決方法です。

バインド機構とは、実際の値を埋め込む場所を記号 (プレースホルダ) で示した SQL 文をあらかじめ準備しておき、後からプレースホルダを実際の値に置き換えて SQL を構築する機構のことをいいます。バインド機構はプレースホルダから実際の値に置き換えるときに、実際の値を正しくエスケープします。

PDO はバインド機構に対応しているので、先に示したサンプルコードを以下のように変更することで、 SQL Injection からアプリケーションを守ることができます:

<?php
// $pdo は PDO のインスタンス
$sth = $pdo->prepare('SELECT * FROM user WHERE username = ? AND password = ?;');
$sth->execute(array($_GET['username'], $_GET['password']));

OpenPNE においては、自分で SQL 文を生成するすべての箇所で SQL Injection に対して配慮をおこなわなければなりません。 OpenPNE ではほとんどの場合直接 SQL 文を書かずに、 Doctrine の DQL 文を直接記述もしくは構築し、その DQL を SQL に変換して実行するということをおこなっていますが、 この DQL も以下のように誤った形で組み立ててしまうと、結局、 SQL Injection に脆弱になってしまいます:

<?php
Doctrine::getTable('User')->createQuery()
  ->where(sprintf('username = "%s" AND password = "%s"', $_GET['username'], $_GET['password']))
  ->execute();

このコードは、バインド機構を利用して DQL を組み立てるために、以下のように記述するべきです:

<?php
Doctrine::getTable('User')->createQuery()
  ->where('username = ? AND password = ?', array($_GET['username'], $_GET['password']))
  ->execute();

一方で、たとえば Doctrine_Table::find() メソッドに関しては、 SQL Injection に対して配慮して SQL 文が生成されるため、引数を渡す際に特別な配慮をおこなう必要はありません。ですが、 Doctrine_Table::findBySql() や Doctrine_Table::findByDql() といった SQL や DQL を自分で組み立てるようなメソッドを利用する場合には、やはり、 SQL Injection に対する配慮が求められることになります。

自分で SQL や DQL を組み立てる必要があり、 SQL Injection に対する配慮が必要なものとしては、たとえば以下のようなものがあります。

  • PDO 以外のデータベース関連拡張が提供する関数群
  • PDO::exec() や PDOStatement::execute() などクエリを実行する PDO のメソッド
  • Doctrine_Connection::fetchAll() など直接 SQL を実行する Doctrine_Connection のメソッド
  • Doctrine_RawSql
  • Doctrine_Query
  • Doctrine_Table::findBySql() など、自分で作成したクエリを元にレコードを取得するようなメソッド

また、バインド機構を利用したとしても、ユーザ入力値に基づいてカラム名などを動的に組み立てるような場合は、 SQL Injection に対して脆弱となります。できるだけそのようなコードは控えるようにするべきですが、それが難しい場合、必ず、動的に組み立てる箇所に対してエスケープや クオート処理を実施してください。

エスケープ等に使用できる Doctrine のメソッドとしては以下のようなものがあります。エスケープ等が必要な記号群やエスケープ手法などはデータベースエンジンによって異なります。そのため、 独自処理を施すより、 Doctrine が用意しているメソッドを利用しておこなうことを強く推奨します。

  • Doctrine_Formatter::escapePattern()
  • Doctrine_Connection::quote()
  • Doctrine_Connection::quoteIdentifier()

安全に外部コマンドを実行する

PHP には外部コマンドを実行可能ないくつかの関数が存在しますが、極力、それらの使用は避けてください。仮に任意の外部コマンドを実行することができる脆弱性 (一般的には OS コマンドインジェクション脆弱性と呼ばれます) が存在してしまっていると、サーバ内ファイルへのアクセスやシステムの操作など非常に多くの、そして危険度の高い脅威が発生しえます。

他の手段で代替することができず、どうしても実行する外部コマンドをユーザ入力値を利用して構築しなければならない場合、ホワイトリストを用いて実 行するべきコマンドをできる限り固定なものにしてください。それも難しい場合、 escapeshellarg() や escapeshellcmd() を使いエスケープしてください。加えて、その入力値の要件にあった適切な入力値検証、フィルタリング (数値文字列を整数型変数に変換するなど) といった保険的な対策をもれなくおこなっておくことを強く推奨します。

意図しないファイルへのアクセスを防ぐ

以下のような、ユーザ入力値を含んだテンプレートファイルへのパスを構築し、そのファイルを読み込むコードを考えます:

<?php

echo file_get_contents('/path/to/template/'.$_GET['name']);

name パラメータの値が hello の場合、 /path/to/template/hello の内容が出力されます。しかし、パラメータの値に ../../../etc/passwd を入力すると、読み込むべきファイルは /path/to/template/../../../etc/passwd となり、 /etc/passwd の内容が出力されてしまいます。このように任意のファイルへのアクセスを許してしまう脆弱性のことを、一般に、ディレクトリトラバーサル脆弱性、または、 パストラバーサル脆弱性などと呼んでいます。

また、ユーザ入力値を基に任意のファイルを書き込むような場合、このディレクトリトラバーサル脆弱性が存在していると、ウェブの公開ディレクトリ直 下にそのファイルを書き込むようにリクエストし、できあがったファイルにアクセスするようユーザを案内することで XSS を発生させる、といったような危険もあります。

ファイルにアクセスする場合には、ユーザ入力値などの信頼できない値を用いてファイル名を指定するような実装は避けるようにしてください。どうして もそのような実装をしなければならない場合、ディレクトリ名は固定とし、ユーザ入力値によって変更できない状態にしたうえで、 basename() 関数をユーザ入力値に対して使い、ファイル名のみを取り出し、この値を基にファイルパスを組み立ててください。

SNS 内情報を安全な形で保存する

SNS の情報は安全な形で保存するように心がけてください。

まず、 SQL Injection 攻撃を受けてしまい、データベースの情報が漏洩してしまったときのために、パスワードなどの情報は salt 付きでハッシュ化してください。また、可能な限りハッシュアルゴリズムには SHA-1 や MD5 よりも SHA-256 や SHA-512 を用いることを推奨します。 SHA-1 や MD5 はクラック手法が発見されてしまっているためです。

XSS 対策としてユーザ入力値を HTML エンコーディングしたうえで DB に格納するといったことも控えてください。アプリケーションで後からこの値を使う際に、その値は入力値として扱われるので、先に述べたとおり、入力時点で のエスケープという「誤ったセキュリティ対策」をしていることになります。入力が HTML エンコーディングで固定されてしまっており、元の値を取得する手段がないとなると、開発者は、この HTML エンコーディングされた文字列を元に戻そうとする (アンエスケープしようとする) ようになることでしょう。ここで、誤ったアンエスケープをしようとして、手違いにより XSS に脆弱な箇所を生んでしまうといった危険が生じ得ます。

アクセス権限の制限の回避を防ぐ

CSRF (Cross Site Request Forgeries) 脆弱性

CSRF 脆弱性は、ユーザに意図しないリクエストを発生させることを強いることができる脆弱性です。

罠が仕掛けられた、そのサイト内あるいはサイト外のページにおいて、 CSRF 脆弱性を悪用したリクエストを発生させるような動作 (リンクのクリックやフォームの送信など) をユーザがおこなうことで、意図しない投稿や設定の変更などを強制的におこなわされてしまいます。

たとえば、 http://example.com/example/{id}/delete という URL (id は単純な連番で予測可能なものであるとします) に対して POST リクエストをおこなうことで、 example の削除処理がおこなわれるという場合、リクエストはたとえば以下のようになります:

POST /example/1/delete HTTP/1.1
Host: example.com
Cookie: PHPSESSID=754d3b148df7a597947f5556cbe06628
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

このリクエストには Cookie の値を除き、秘密情報はどこにも含まれていません。ですので、 /example/1/delete という URL に POST リクエストを実行させれば削除処理をおこなわせることができる、ということが知られてしまえば、ユーザがこの POST リクエストを発行するようなフォームを罠ページなどで実行してしまうことで、ユーザは意図せずに example を削除してしまうことになります。このリクエストを受けてアプリケーション側で削除処理を実行してしまってはいけません。

そこで、リクエストに秘密情報を要求するようにすることで、 CSRF を防ぐことができます。リクエストに秘密情報を含める対策の例として、以下のようなものがあります。

  1. セッション ID を hidden フィールドの値として入れるなどしてリクエストに含む
  2. ユーザのパスワードを入力させる
  3. 独自の予測不可能な ID を hidden フィールドの値として入れるなどしてリクエストに含む

一般に用いられるのは 1. もしくは 3. です。 OpenPNE では 1. に基づいた 3. を、この後に説明するフォームフレームワークの機構を用いて生成することにより対策してください。

ただし、 1. や 3. の場合、そのサイト内に XSS 脆弱性が存在する場合、容易に回避することが可能になります。そのため、本当に重要な操作については、 2. の対策もあわせて実施することを強く推奨します。

フォームにおける対策

symfony のフォームフレームワークを利用してフォームのレンダリングやリクエストのバリデーションをおこなうことで、結果的に CSRF を防ぐことができます。ですので、フォームを記述する際はフォームフレームワークを利用して書くようにしてください。

sfForm およびその派生クラスは、「OpenPNE.yml で設定できる csrf_secret の値 (デフォルト値は設定ファイルのタイムスタンプ)」と、「セッション ID」、そして「そのフォームのクラス名」を文字列連結したものの MD5 ハッシュ値を、 CSRF トークンとして提供します。

この CSRF トークンがリクエスト中に含まれているかどうかをもって、フォームクラスが適切なリクエストかどうかを判断します。

フォームフレームワークについては http://www.symfony-project.org/gentle-introduction/1_4/ja/10-Forms を参照してください。

なお、この CSRF 保護の機構を無効にする手段がいくつか存在します。これは、 CSRF への対策の必要がない種類の操作 (たとえば、実行してもユーザや SNS 内データに影響しない操作) などにおいて、 CSRF トークンのチェックが邪魔になるようなときに有用ですが、 CSRF 保護の機構を無効にする場合は充分によく考えてから実施するようにしてください。

アクション内での対策

フォームを使用しないアクションであっても、 CSRF 対策が必要な場合にはフォームフレームワークを使用して対処することができます。

まず、リクエストをする側のアクションもしくはテンプレートで、 BaseForm (sfForm の派生クラスで、プロジェクトのほとんどのフォームの親クラス) のインスタンスを生成し、普段フォームフレームワークを使用するのと同じように BaseForm をレンダリングします。

フォームによってリクエストを実行したくない場合は、リクエストパラメータに BaseForm のインスタンスから得られる CSRF トークンを含めるように明示的に指定する必要があります。パラメータ名は BaseForm::getCSRFFieldName()、 CSRF トークンの値は BaseForm::getCSRFToken() で取得できます。

リクエストを処理する側のアクションでは、以下のように sfWebRequest::checkCSRFProtection() を実行するだけで、 CSRF トークンのチェックをおこなうことができます:

<?php

class exampleActions extends sfActions
{
  public function executeDelete(sfWebRequest $request)
  {
    $request->checkCSRFProtection();
  }
}

sfWebRequest::checkCSRFProtection() は、正しい CSRF トークンがリクエストに含まれている場合は何もしません。リクエストに含まれている CSRF トークンに問題があるか、存在しない場合 sfValidatorErrorSchema のクラスインスタンスを例外として throw します。

この例外はアクションを実行している opExecutionFilter によって透過的に catch し、適切なエラーメッセージを出力するので、アクション側で catch をするなどして特別な配慮をおこなう必要はありません。

Ajax リクエストにおける対策

うっかり忘れがちなことですが、 Ajax リクエストにおいても、 CSRF 対策が必要になる場合があります。

対処方法は「アクション内での対策」と同様、 BaseForm から得られる CSRF トークンの値をリクエストに含むことです。

クライアントサイドでのバリデーション、つまり JavaScript によってポストしようとしている CSRF トークンをチェックするようなことは、セキュリティ上意味をなさないことを念頭に置いてください。必ずサーバサイドでチェックをおこなうようにしてくださ い。

アクションで認証を要求する

単純にアクションを作っただけでは、そのアクションは認証を要求しません。つまり、そのアクションは未ログイン状態のユーザでも実行可能ということになります。

認証を要求するアクションを作りたい場合は、以下のうちどちらか一方の作業をおこなってください。

  1. security.yml を作り、アクション実行前に自動的に認証をおこなうようにする。
  2. アクション内に、自分で認証のためのコードを記述する。

security.yml については http://www.symfony-project.org/gentle-introduction/1_4/ja/06-Inside-the-Controller-Layer#chapter_06_fa99a6d638f334600f0681315182141a32d28123 を参照してください。

権限チェックの漏れを防ぐ

メンバーがそのアクションを実行可能な権限を持っているかどうか、もしくはそのアクションで実行される一部の表示要素にアクセスする権限を持ってい るかどうか、などは必ず念入りに確認をおこなってください。フレンド関係にないのにフレンドのみに公開された情報を閲覧できる、などといったことがないよ うに気を配ってください。

権限チェックは可能な限り共通化してから呼び出すようにし、漏れが生じにくいように徹底してください。

また、 OpenPNE では、いくつかのレコードクラスと Zend_Acl を組み合わせて、そのリソースに対するアクセス権限チェックがおこなえるような仕組みを用意しているので、これを利用することもできます。アクション実行 前にリソースへのアクセス権限を有しているかどうかを確認させたい場合は、 opDynamicAclRoute クラスを使ったルーティングルールを記述してください。テンプレート中などで、特定のレコードクラスのインスタンスに対して、現在閲覧中のメンバーがその リソースにアクセス可能かどうか調べるには、レコードクラスのインスタンスメソッド isAllowed() を使用してください。

引用元

更新:2011/02/23 00:22 カテゴリ: OpenPNE3  > セキュリティ ▲トップ
1件中 1 〜 1 表示  1 

FuelPHP

Mac

web開発

プロマネ

マネタイズ

プレゼン

webサービス運用

webサービス

Linux

サーバ管理

MySQL

ソース・開発

svn・git

PHP

HTML・CSS

JavaScript

ツール, ライブラリ

ビジネス

テンプレート

負荷・チューニング

Windows

メール

メール・手紙文例

CodeIgniter

オブジェクト指向

UI・フロントエンド

cloud

マークアップ・テキスト

Flash

デザイン

DBその他

Ruby

PostgreSQL

ユーティリティ・ソフト

Firefox

ハードウェア

Google

symfony

OpenPNE全般

OpenPNE2

Hack(賢コツ)

OpenPNE3

リンク

個人開発

その他

未確認

KVS

ubuntu

Android

負荷試験

オープンソース

社会

便利ツール

マネー

Twig

食品宅配

WEB設計

オーディオ

一般常識

アプリ開発

サイトマップ

うずら技術ブログ

たませんSNS

rss2.0