Bug(バグ) #2440
完了携帯版で外部サイトのURLへリダイレクトする処理を記述すると、リダイレクト先のURLにセッションIDが付与されてしまう場合がある
100%
説明
Overview (現象)¶
携帯版(mobile_frontend アプリケーション内)で外部URLへリダイレクトする処理を記述すると、Cookie を利用できない携帯端末でアクセスした際に、リダイレクト先の URL のパラメーターとしてセッションIDが自動的に付与されてしまいます。
例えば、アクション内に $this->redirect('http://externalsite.example.com/');
のように記述した場合がこれに該当し、このときリダイレクト先の URL が http://externalsite.example.com/?OpenPNE_mobile_frontend=XXX
のようになってしまいます。
mobile_frontend のアクション内でこの問題を回避してリダイレクト処理を記述するには、Location ヘッダーを送信する処理を自前で記述する方法くらいしか思いつかず、mobile_frontend 内にリダイレクト処理を記述している場合のほとんどこの問題の発生条件に該当するのではないかと思います。
OpenPNE 3.6RC1 で現象を確認しましたが、原因となっている箇所は OpenPNE 3.0 〜 3.7 まで同じ実装となっており、現行の OpenPNE 3 のすべてのバージョンで発生すると思われます。
携帯版で信頼できない外部URLへリダイレクトする際には、既存のセッションを無効にした上でおこなう必要があり、その場合には付与されたセッションIDによる攻撃は成立せず、セキュリティ上の問題はありません。(また、たとえ本チケットの問題を修正したとしても、リファラーからセッションIDが漏えいする危険があるため、既存のセッションが有効な状態で携帯版のログイン後ページから信頼できない外部URLへ直接リダイレクトしてはなりません。)
ただし、付与されるのが無効なセッションIDであったとしても、不要なURLパラメーターが強制的に付与されてしまうのは問題であるため、通常のバグとして報告しました。
Causes (原因)¶
opMobileFrontWebController::redirect() で渡された URL の内容によらず、強制的にリダイレクト先の URL パラメーターにセッションIDを追加しているため。
Way to fix (修正内容)¶
opMobileFrontWebController::redirect() に渡された URL が絶対URLである場合、URL は外部サイトを指していると仮定して、セッションIDのパラメーターをリダイレクト先のURLに付与しないよう修正します。
絶対URLであるからと言って外部サイトを指しているとは限らないですし、逆に絶対URLでないからと言って外部サイトを指していないとも限らないですが、本チケットでの修正対象を絶対URLの場合としたのは、PHP の session.use_trans_sid の仕様に合わせるためです。
(参考)PHP: セッションIDの受渡し
http://www.php.net/manual/ja/session.idpassing.php
ここで絶対URLというのは、 RFC 3986 : 4.3. Absolute URI で定義されている absolute-URI の条件を満たすURLで、scheme(http や https)から始まるURLです。
よって、以下のようなURLについてはこのチケットでは修正対象としません。
- foo (相対パス)
- /bar (絶対パス)
- //hoge.com (スキームを省略したURL)
これらはいずれも PHP の session.use_trans_sid でも相対URL扱いをされ、セッションIDが付与される対象となります。(このうち、相対パス形式の指定は redirect() メソッドの引数として渡した場合は、symfony の内部 URI とみなされるため HTML 内に直接書いた場合とは意味が異なりますが、セッションIDが付与されるかどうかの判定に関しては同じ結果となります。)
この修正以後も、開発者は mobile_frontend 内で絶対URL以外の形式で外部サイトを指す URL を安易に利用するべきではありません。これはリダイレクト先として指定する URL に限らず、PHP の session.use_trans_sid で変換対象となる HTML 内の URL にも当てはまります。
一方、この修正により、サイト内部の URL を絶対URLで記述している場合、セッションIDのパラメーターが付与されなくなるため、正常に動作しなくなる可能性があります。ただ、 redirect() メソッドを利用する際に OpenPNE (symfony) 内部の URL を指定するのに絶対URLを使うメリットはほとんどないと思われるため、このようなケースは珍しいと考えられます。仮にこのようなケースに該当する場合は、symfony の内部 URI 形式を利用するようにするか、もしくは、自力でセッションIDを付与するようにする必要があります。
Rimpei Ogawa さんが約13年前に更新
- ステータス を New(新規) から Accepted(着手) に変更
- 担当者 を Rimpei Ogawa にセット
- 対象バージョン を OpenPNE 3.7.0 にセット
Rimpei Ogawa さんが約13年前に更新
- ステータス を Accepted(着手) から Pending Review(レビュー待ち) に変更
- 進捗率 を 0 から 50 に変更
更新履歴 021894b966bdf7c6e3dbde0e7c2aa966c256eee5 で適用されました。
Kousuke Ebihara さんが約13年前に更新
- ステータス を Pending Review(レビュー待ち) から Pending Testing(テスト待ち) に変更
- 進捗率 を 50 から 70 に変更
Minoru Takai さんが約13年前に更新
- ステータス を Pending Testing(テスト待ち) から Rejected(差し戻し) に変更
- 進捗率 を 70 から 50 に変更
この修正について一点疑問があります。このコメントの下部に疑問を示しているので確認頂きたいです。
note-2 の修正について¶
- lib/vendor/symfony/lib/controller/sfWebController.class.php には次のような記述があります。
31- public function genUrl($parameters = array(), $absolute = false) 32- { 33- $route = ''; 34- $fragment = ''; 35- 36- if (is_string($parameters)) 37- { 38- // absolute URL or symfony URL? 39- if (preg_match('#^[a-z][a-z0-9\+.\-]*\://#i', $parameters)) 40- { 41- return $parameters; 42- } 43- 44- // relative URL? 45- if (0 === strpos($parameters, '/')) 46- { 47- return $parameters; 48- } :
この sfWebController クラス内の記述を参考にして次のように修正されています。
- note-2 での修正内容
public function redirect($url, $delay = 0, $statusCode = 302) { + // absolute URL or symfony URL? + if (is_string($url) && preg_match('#^[a-z][a-z0-9\+.\-]*\://#i', $url)) + { + return parent::redirect($url, $delay, $statusCode); + } + $url = $this->genUrl($url, true);
URL 記述に関して¶
- URL の文法については RFC 2396, RFC 3986 等で定められています。
- http://www.ietf.org/rfc/rfc2396.txt http://tools.ietf.org/html/rfc2396 Uniform Resource Identifiers (URI): Generic Syntax
- http://www.ietf.org/rfc/rfc3986.txt http://tools.ietf.org/html/rfc3986 Uniform Resource Identifier (URI): Generic Syntax
- RFC 2396 : Page 26 から引用
A. Collected BNF for URI URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] absoluteURI = scheme ":" ( hier_part | opaque_part ) relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] hier_part = ( net_path | abs_path ) [ "?" query ] opaque_part = uric_no_slash *uric uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," net_path = "//" authority [ abs_path ] abs_path = "/" path_segments rel_path = rel_segment [ abs_path ] rel_segment = 1*( unreserved | escaped | ";" | "@" | "&" | "=" | "+" | "$" | "," ) scheme = alpha *( alpha | digit | "+" | "-" | "." )
- 2011/11/01 追記: URI-reference などの上記の定義は RFC 3986 : Page 48 で改定されているようです。
URL として次のようなものがあるようです(ここでは URI ではなく URL と示しておきます)。
- foo (相対パス)
- /bar (絶対パス)
- //hoge.com (スキームを省略した絶対URL)
- http://fuga.com (絶対URL)
スキームを省略した絶対URL(これは net_path と示されており、相対URLの一種のようです)の場合、そのURLのスキームは、ベースとなるURLのスキームを継承するようです。このことは RFC 2396 で示されています。
- RFC 2396 : Page 19 - 5.2. Resolving Relative References to Absolute Form
3) If the scheme component is defined, indicating that the reference starts with a scheme name, then the reference is interpreted as an absolute URI and we are done. Otherwise, the reference URI's scheme is inherited from the base URI's scheme component.
なお、 net_path については RFC 3986 で、これは network-path reference というものであると示されています。
- RFC 3986 : Page 25 - 4.2. Relative Reference
relative-ref = relative-part [ "?" query ] [ "#" fragment ] relative-part = "//" authority path-abempty / path-absolute / path-noscheme / path-empty ---- A relative reference that begins with two slash characters is termed a network-path reference;
リダイレクト先に指定できるURLに関して¶
- HTTP/1.1 の規格については RFC 2616 等で定められています。
- http://www.ietf.org/rfc/rfc2616.txt http://tools.ietf.org/html/rfc2616 Hypertext Transfer Protocol -- HTTP/1.1
リダイレクトは Location レスポンスヘッダフィールドを使用して行われますが、 HTTP/1.1 ではここに指定できる URI は absoluteURI のみと定められているようです。
- RFC 2616 : Page 134 - 14.30 Location
14.30 Location The Location response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request. For 3xx responses, the location SHOULD indicate the server's preferred URI for automatic redirection to the resource. The field value consists of a single absolute URI. Location = "Location" ":" absoluteURI An example is: Location: http://www.w3.org/pub/WWW/People.html Note: The Content-Location header field (section 14.14) differs from Location in that the Content-Location identifies the original location of the entity enclosed in the request. It is therefore possible for a response to contain header fields for both Location and Content-Location. Also see section 13.10 for cache requirements of some methods.
しかしながら、実際には相対 URL を受け付けるクライアントが多いようです。これについては PHP マニュアルにも示されています。
- PHP: header - Manual の注意のセクション (いくつかある注意のうち 6 個目あたり)
HTTP/1.1 では、スキーム、ホスト名、絶対パスを含む絶対 URI が » Location: の引数として必要ですが、相対 URI を受け付けるクライアントもあります。 通常は、相対 URI から絶対 URI を作成するためには $_SERVER['HTTP_HOST']、 $_SERVER['PHP_SELF'] および dirname() を使用できます。
簡単な動作テストの結果¶
本件の問題を再現させ、修正が適切に行われているかどうかを確認するため、次のような差分を当てました。
- 携帯版でプロフィールページ(他人、あるいは自身のプロフィール確認ページ)へアクセスするとリダイレクトする変更の差分
diff --git a/apps/mobile_frontend/modules/member/actions/actions.class.php b/apps/mobile_frontend/modules/member/actions/actions.class.php index afa97b6..7ee7da2 100644 --- a/apps/mobile_frontend/modules/member/actions/actions.class.php +++ b/apps/mobile_frontend/modules/member/actions/actions.class.php @@ -55,6 +55,14 @@ class memberActions extends opMemberAction public function executeProfile($request) { + $url = array( + 1 => 'www.openpne.jp/', // rel_path (relativeURI) 相対パス参照(相対URI) + 2 => '/www.openpne.jp/', // abs_path (relativeURI) 絶対パス参照(相対URI) + 3 => '//www.openpne.jp/', // net_path (relativeURI) ネットワークパス参照(相対URI) + 4 => 'http://www.openpne.jp/', // absoluteURI 絶対URI + ); + $this->redirect($url[4]); + $this->friendsSize = 5; $this->communitiesSize = 5;
この差分を適用し、 Firefox の FireMobileSimulator を用いて動作確認した結果は次の通りです。(非SSL環境の http://sns.example.com/ で確認したものと考えてください)
修正前 | 修正後 | |
1 => 'www.openpne.jp/' 本件に関係ないテスト |
外部URLを見做されず http://sns.example.com/index.php/www.openpne.jp/?OpenPNE_mobile_frontend=xxx に遷移する | 修正前と同じ |
2 => '/www.openpne.jp/' 本件に関係ないテスト |
外部URLを見做されず http://sns.example.com/www.openpne.jp/?OpenPNE_mobile_frontend=xxx に遷移する | 修正前と同じ |
3 => '//www.openpne.jp/' 外部URLでのテスト |
http://www.openpne.jp/?OpenPNE_mobile_frontend=xxx にリダイレクトする | 修正前と同じ |
4 => 'http://www.openpne.jp/' 外部URLでのテスト |
http://www.openpne.jp/?OpenPNE_mobile_frontend=xxx にリダイレクトする | http://www.openpne.jp/ にリダイレクトする |
1, 2 のテストは本件の問題に関係ないので無視してもよいものです。問題は 3, 4 のケースについてです。
ついでに、これ以外の記述で外部URLを記述できる可能性があるかについても気になりましたが、それは無いと思っています。
このチケットで扱う内容について¶
チケットの概要に書かれていますが、このチケットで扱っている内容は「開発者」が「例えば、アクション内に $this->redirect($url); のように記述した場合」に生じる問題についての修正のようです(つまり、現状の OpenPNE に問題があるわけではないようです)。
開発者が(カスタマイズ、あるいは将来の OpenPNE の実装として)、「携帯版で外部URLへのリダイレクトする処理を記述すると、リダイレクト先のURLにセッションIDが付与されてしまう場合がある」ので、 「opMobileFrontWebController::redirect() に渡された URL が絶対URLである場合、セッションIDのパラメーターをリダイレクト先のURLに付与しないよう修正する」と書かれています。
【疑問】上記の動作テストの表で、修正後の列をみると、動作が変更(改善)されているのは 4 のケースのみですが、このチケットでの修正はこれが意図した結果でしょうか。 3 のケースは考慮しなくてよいものでしょうか。
絶対URLの場合としたのは、PHP の session.use_trans_sid の挙動に合わせるためで、実装上問題がなければ絶対URLかつ外部URLの場合だけに限ってもよい。
という検討を行った経緯が読み取れる記述はありますが、これは「内部URLだが絶対URLである場合」を例外としていないことに対するコメントで、「外部URLだがスキームを含む絶対URLでない(つまり net_path 形式である)場合」を加味した上でのコメントではないと受け取っています。
チケットの文章からは本件の修正意図が読み取れなかったので、実装者がどのような意図で(何さえ解消できればよいものとして)修正を行ったのかについて、上記の疑問に回答頂ければと思います。また、可能であれば、本件についての動作テスト手順(私が上記で行った手順は必要十分なのか)についても示して頂けると幸いです。
Rimpei Ogawa さんが約13年前に更新
- 題名 を 携帯版で外部URLへリダイレクトする処理を記述すると、リダイレクト先のURLにセッションIDが付与されてしまう場合がある から 携帯版で外部サイトのURLへリダイレクトする処理を記述すると、リダイレクト先のURLにセッションIDが付与されてしまう場合がある に変更
- 説明 を更新 (差分)
- ステータス を Rejected(差し戻し) から Accepted(着手) に変更
note-4 についてコメントします。
まず、
これは「内部URLだが絶対URLである場合」を例外としていないことに対するコメントで、「外部URLだがスキームを含む絶対URLでない(つまり net_path 形式である)場合」を加味した上でのコメントではないと受け取っています。
についてはその通りです。「外部URLだがスキームを含む絶対URLでない(つまり net_path 形式である)場合」の考慮は漏れていました。この形式について追加でいくつか調査しました。
PHP の session.use_trans_sid¶
「外部URL」ではなく「絶対URL」に限った実装にした根拠は Description に記載していた通り、PHP の session.use_trans_sid の挙動に合わせるためでした。OpenPNE の mobile_frontend アプリケーションでは session.use_trans_sid が有効にされています。
マニュアルの該当ページ に記載されている通りの挙動かどうかを確かめるため、以下のPHPスクリプトを設置してどのURLが変換されるかを確認しました。
<?php ini_set('session.use_cookies', 0); ini_set('session.use_only_cookies', 0); ini_set('session.use_trans_sid', 1); session_start(); ?> <!doctype html> <html><head><title>TRANS SID TEST PAGE</title></head><body><ol> <li><a href="example.com/">example.com/</a></li> <li><a href="/example.com/">/example.com/</a></li> <li><a href="//example.com/">//example.com/</a></li> <li><a href="http://example.com/">http://example.com/</a></li> </ol></body></html>
結果は 1〜3 には SID 付与、4 のみ SID なしとなりました(PHP 5.3.8, 5.2.13 で確認)。
note-4 での指摘通り、3 の形式は絶対URLではなくても外部URLである可能性があるため、この挙動では外部サイトを指す URL にセッションIDが付与されてしまう危険性があります。ただ、この「外部サイトを指す」かどうかは単純にURLの形式から判定できるものではなく、同じホスト名でも別のサイトを指すこともあれば、別のホスト名で同じサイトを指すこともあり、そもそも PHP のレイヤーで完全に判別できるものではありません。つまり、1 や 2 の形式であっても外部サイトを指す可能性があるため、3 の形式と同様に PHP プログラマー側で外部サイトの指定に使用しないよう気をつける必要があります。
リダイレクトに使用される URL には session.use_trans_sid は適用されませんが、これと同様の配慮でよいという判断ができれば note-2 の実装でも十分と見ることができます。
symfony の sfWebController::genUrl()¶
sfWebController::genUrl() の実装を見直すと、第二引数 $absolute に true が渡された場合でも、第一引数 $parameters に先頭が /
で始まる文字列が指定された場合には $parameters がそのまま返り値となってしまい、絶対URLにならない実装となっています。
第一引数の説明には、
An associative array of URL parameters or an internal URI as a string
とあり、そもそもここに symfony の内部 URI 以外を文字列で指定することは想定されていないのかもしれません(とはいえ、実装上は絶対URLの判別や、問題はありますが /
で始まる相対URLなどに関する例外処理が記述されています)。そのため、このメソッドを内部で利用している redirect() メソッドにも、symfony の内部 URI や(実装上動作に問題のない)絶対URL以外の文字列を指定しないようにした方がよさそうです。
note-4 の 3 の例で、 ->redirect('//www.openpne.jp/')
のような呼び出しに対し Location: //www.openpne.jp/
というヘッダーを返すのは symfony で意図された挙動ではないと思われ、このような呼び出し方法は利用しない方が安全だと思います。
PHP と比較して symfony の内部の挙動については変更が容易であるため、redirect() や genUrl() をより多様な URL 指定でも動作するよう修正することも可能だとは思いますが、本チケットではそこまで修正対象を広げる必要はないように思います。
本チケットでの修正内容について¶
note-2 の実装で十分と考えます。
理由としては繰り返しになりますが、PHP の session.use_trans_sid と同じ基準でセッションIDの付与が行われていることと、symfony の sfWebController::genUrl() が net_path 形式の URL を元々正しく扱えないことになります。
修正内容と修正後の注意点について Description に追記しました。
補足:Location ヘッダーへの相対URL指定について¶
参考情報として補足しますが、過去に携帯実機の検証をした際に Location ヘッダーに相対URLを指定した場合に動作しない端末が存在した記憶があります。具体的にどの端末で動作しなかったは覚えていないため、もしかすると非常に古い端末のみかもしれませんが、このことに配慮するならば Location ヘッダーには正しく絶対URLを指定するようにした方がよいと思います。
Rimpei Ogawa さんが約13年前に更新
- ステータス を Accepted(着手) から Pending Review(レビュー待ち) に変更
実装者としては note-2 の修正で十分と考えたため、ステータスをレビュー待ちに変更します。
Minoru Takai さんが約13年前に更新
- ステータス を Pending Review(レビュー待ち) から Pending Testing(テスト待ち) に変更
- 進捗率 を 50 から 70 に変更
note-5 での説明ありがとうございます。 note-4 での疑問については note-5 によって解消されています。
note-3 で既にレビューされており、私としても(修正意図に対して)修正内容は妥当だと判断しているのでテスト待ちにします。
Shouta Kashiwagi さんが12年以上前に更新
- 3.6 で発生するか を Unknown (未調査) にセット
- 3.4 で発生するか を Unknown (未調査) にセット
このチケットについての、検証用プラグインを作成しました。
http://github.com/kashiwasan/opRedirectTest2440Plugin
【インストール方法】
$ cd plugins/ $ git clone git://github.com/kashiwasan/opRedirectTest2440Plugin.git $ cd ..
【テスト方法】
0. 携帯版でログインし、http://<base_url>/redirectTest/index にアクセスする。
1. テキストフィールドに描かれているURLが、「 http://<base_url>/redirectTest/url 」となっていることを確認し、「送信」ボタンを押す。
2. リダイレクト先のページで、携帯のメニュー(ページ情報等)から現在のURLを表示し、URLにセッションIDが付与されていないかを確認する。
Yuma Sakata さんが12年以上前に更新
- ステータス を Pending Testing(テスト待ち) から Fixed(完了) に変更
- 進捗率 を 70 から 100 に変更
テストOKです。