プライベートサイトを作るならPHPのURL Rewriterを使う

(更新日: 2016/10/27)

クロスサイト・リクエスト(他のWebサイトから自分のサイトへURLリンクやPOSTでアクセスする)を制限したいWebアプリケーションは結構あります。例えば、ホームルーターの管理ページを作る場合、クロスサイト・リクエストは有用などころか有害です。ホームルーターの管理ページにはクロスサイト・リクエストによる脆弱性が多数報告されています。

PHPの基本機能を使えば、クロスサイト・リクエストを簡単かつ丸ごと拒否することができます。CSRFやXSS1を完全かつ簡単に拒否する仕組みを作れます。

URL Rewriterの仕様

URL Rewriterを使えば、簡単にWebサイトをクロスサイト・リクエストから保護できます。

URL RewriterはURL(http://example.com/file.phpやHTMLフォーム)に自動的に任意の変数を追加します。追加するにはoutput_add_rewrite_var関数を利用します。

output_add_rewrite_var

(PHP 4 >= 4.3.0, PHP 5, PHP 7)

output_add_rewrite_varURL リライタの値を追加する

説明

bool output_add_rewrite_var ( string $name , string $value )

この関数は、URL リライト機構に新しい名前/値の組を追加します。 名前および値は、URL (GET パラメータとして) およびフォーム (hidden フィールドとして) で追加されます。これは、session.use_trans_sid で透過的 URL リライティングが有効になっている場合に セッション ID が渡される方法と同じです。

この関数の挙動は、php.ini パラメータ url_rewriter.tags および url_rewriter.hosts によって制御されます。

注意: もし出力バッファリングが有効になっていない場合、この関数を コールすると出力バッファリングが暗黙的に開始されます。

 

このマニュアルページはPHP 7.1の説明になっています。私がURL Rewriterのバグを修正し、仕様を拡張(url_rewriter.hosts設定、専用の出力バッファを追加)したので、最近英語ページを更新したのですが日本語ページも翻訳されています。素晴らしい!

PHP 7.1未満の場合、出力バッファがSessionのTrans SID(透過的セッションID – URLやPOSTに自動的にセッションIDを追加する機能)と共有され、INI設定(url_rewriter.tags)も共用しています。リライト用の出力バッファーが共用されているに注意してください。2

PHP 7.1以降はSession用のINI設定名が異り(session.trans_sid_tags, session.trans_sid_hosts)、利用する出力バッファもそれぞれ専用なので注意する必要がありません。

output_add_rewrite_var関数を使ったクロスサイト・リクエストの拒否

クロスサイト・リクエストを丸ごと拒否する仕組みはこうなっています。

  • output_add_rewrite_var()でリクエストをバリデーションするトークンを追加する
  • リクエストを受け付けるPHPプログラムでトークンをバリデーションする

たったこれだけです。(注意:PHP 7.1以降が必要です)

リクエストトークンの作成

リクエストトークンのバリデーション

どこかに少なくとも一箇所のエントリポイントが必要です。エントリポイントではトークンのバリデーションは省略します。エントリポイントはクロスサイト・リクエストを防止できないので、クロスサイト・リクエストでも問題ないページにします。エントリポイント以外でバリデーションすればサイト全体がクロスサイトリクエストから保護されます。

 

JavaScriptからのリクエスト

JavaScriptからリクエストする場合、サーバーにトークンを返すAPIを作って取得してはいけません。JavaScriptからリクエストする場合、クエリ文字列の中にあるrtokenを利用します。

こうしないとクロスサイト・リクエストを防止できません。

 

直ぐに使えるように改良

先程のサンプルコードは解りやすいように2つに分けましたが、一つにまとめてauto_prepend_fileで使えるようにします。(注意:PHP 7.1以降が必要です)

site_protect.php – auto_prepend_fileで読込む

 

index.php ー テスト用のエントリポイント

 

protected.php – テスト用の保護されたスクリプト

 

GitHub

このブログのスクリプトとは少し異なりますが、GitHubに入れておきました。こちらはPHP 5.6/7.0でも動作します。

https://github.com/yohgaki/no-cross-site-requests

 

テスト

上記の3つファイルを適当なディレクトリに置き

php -S 127.0.0.1:8888

としてビルトインWebサーバーを起動します。

http://127.0.0.1:8888/index.php

にアクセスすると

と画面に表示されます。index.phpはホワイトリストで許可したファイルなので、トークン無しでもアクセスできます。

protected.phpのリンクをクリックと以下のようなURLになり、URLにトークンが自動的に付加されていることが判ります。

rtokenを削除したり、改ざんしたりすると

と表示されtokenを知らない第三者がアクセスできないことがわかります。第三者が罠ページを作成して、CSRF攻撃、XSS攻撃を行おうとしても出来なくなります。

 

まとめ

この仕組みはルーターの様なデバイス、開発環境で誰でもアクセスできるツールなどを守る為に便利です。クロスサイト攻撃から守る為にはログインなどの認証も必要ありません。内部ネットワークからアクセスしかできない場合、認証機能が無くても保護できます。

画像やその他のファイルも守りたい場合、イメージファイルなども全てPHPで処理し、クロスサイト・リクエストでない場合にのみファイルを送信すれば保護できます。

古いPHPでも同様にリクエストをバリデーションする仕組みを作れますが、できればPHP 7.1以降で利用することをお勧めします。またsession_start()を呼び出しているので、アプリケーションがsession_start()を呼んでいる場合、既にセッションがある、とE_NOTICEエラーが出ます。普通、session_regenerate_id()はアプリケーションが呼ぶので、新しいトークンの登録が行われません。多少の改造が必要になります。

フレームワークのアプリケーションなどではエントリポイントが1つになっている場合が多いでしょう。この場合、スクリプトを少し改造してREQUEST_URIなどを利用し、特定のURIだけ保護する、などの対応が必要です。

管理ツールもクラウド上にある、という場合はIPアドレスで制限する、HTTP認証でも何でも良いので認証を付けてエントリポイントを保護する、のどちらかをすれば良いです。

URLとHTML Formには1つ値(rtoken)が追加されます。厳格に入力バリデーションしている場合、バリデーションコードの調整も必要になります。

これらの点に注意が必要ですが、とても簡単により安全なプライベートサイトが作れることが解ったと思います。


  1. 本来の意味のXSS(クロスサイトでJavaScriptをインジェクションする)攻撃 
  2. url_rewriter.tagsを変更すると、trans sidにも影響します。リライト変数を削除する関数は全ての変数を削除する関数しか用意されていません。リライト変数を削除するとtrans sidによるセッションIDのリライトも削除されます。 

Comments

comments

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です