symfony1.0.Xにおいてurl_forヘルパーのXSS脆弱性の可能性
symfonyにはrouting.ymlでの設定名を利用してサイト内のURLを返してくれるurl_forというヘルパー関数がありますがXSS脆弱性らしきものを見つけたので共有がてら書き留めておきます。
XSSが起こるケースはurl_forの第2引数にtrueを指定して絶対パスを返すようにしているときに起こります。バージョンはsymfony1.0.21PRE、ブラウザはIE7で確認しました。
再現方法はtelnetでsymfonyで実装していて url_for('@foobar', true) を使っているページに下記のようにアクセスします。ここでは例としてexample.comの/foo/id/1というURLにアクセスしています。
$ telnet telnet > open example.com 80 Trying 192.168.1.1... Connected to example.com. Escape character is '^]'. GET /foo/id/1 HTTP/1.1 Host: example.com""" style="background:url(javascript:alert(631))" "
上記コマンドを実行すると、url_forは下記のようなHTMLを返します。
<a href="http://example.com""" style="background:url(javascript:alert(631))" "></a>
alertが実行されてしまいました。
原因を調べるためにsymfonyのソースコードを見ていきます。まず、url_forの実装は [symfony_lib_dir]/helper/UrlHelper.phpの39〜48行目にあります。
<?php function url_for($internal_uri, $absolute = false) { static $controller; if (!isset($controller)) { $controller = sfContext::getInstance()->getController(); } return $controller->genUrl($internal_uri, $absolute); }
実際にURL生成しているのはControllerクラスのgenUrlメソッドです。genUrlの実装は[symfony_lib_dir]/controller/sfWebController.class.php の31〜124行目あたりです。ここでは113〜117行目を抜粋します。
<?php if ($absolute) { $request = $this->getContext()->getRequest(); $url = 'http'.($request->isSecure() ? 's' : '').'://'.$request->getHost().$url; }
絶対パスを生成する場合、getHostメソッドでクライアントからのリクエストをそのまま使っているのが問題になるみたいです。弊害をあんまり考慮せずに安直に解決するのであればこんな感じでしょうか。
<?php if ($absolute) { $request = $this->getContext()->getRequest(); $url = 'http'.($request->isSecure() ? 's' : '').'://'.htmlspecialchars($request->getHost(), ENT_QUOTES).$url; }
symfony1.2.Xがどうなってるのか、また公式のTracチケットもまだ見ていないのでもしかしたらもう報告され対応される問題かもしれません。サイト内のリンクでは絶対パスをあまり使うことが無いと思いますがもし利用する際はご注意ください。