Google Chrome のXSS仕様にはまる。

このページは、WordPressのプラグインを作成している方や、テーマ内のfunctions.phpより独自の設定管理を設けている方には参考になるのではないかと思っています。

もしかしたら私だけの影響かもしれませんが。

(その際はコメントフォームに何か一言言ってもらえると嬉しいです 🙂 )

もし試してみたいという方は、下記よりお試し下さい。
(ブラウザの影響と私は仮定しています。なのでブラウザが更新されて仕様が変わった場合は、別の動作になるのかもしれません。この現象はこの記事を書いている 2013年8月ごろの、とても暑い日が続く日の事です 😳 )

 

いきさつ

かなりの時間。はまりました。

とあるバグを報告してくれた方から、「あなたのプラグインでこの部分が設定できないよ。他は動くんだけどねとご報告がありました。

他は動く…?

ということは、動かない部分の記述や分岐が間違っていたりフックするタイミングや処理が間違っていたりするだろうな。と想定していました。

なので、そのような考えのもとで原因を探していました。

でもどれだけ試しても、バグの報告をしてくれた方と同じような状況にならず、想定通りの動作をしました。

う~ん、どうしたもんだろう。。とそういえば、以前動画ファイルでバグの症状を報告してくれた方がいました。

その方の動画を参考に、どのようなプラグインをインストールしているのかを調べ、できる限り同じような環境を作りました。

それで初めて、バグの症状が分かりました。

一言で言えば他プラグインの影響。と言ってしまえばそれまでですが。

しかし、有名なJetpackプラグインを使用している時、または WooCommerce Admin Bar Addition を使用している時のバグでした。
(どちらもプラグイン側のバグではありません)
 

で、色々と調べてみると

どうも、<form>タグ関係の場合だけ、バグが出るという症状でした。

どんどん調べていくと、結論としては、Google ChromeのXSS対策の仕様?と判断しました。 😳
正確にはプラグインの影響ではなく、そのプラグインの影響を可能にした場合の<form>タグ関連の影響でした。
(WordPressを一切使わず、シンプルなHTMLだけの場合でも試してみました)

これはプラグインうんぬんではなく、様々な場合に影響があると思っています。
「投稿」「固定ページ」等も含まれます。でも公開はできますが)

この症状を試してみたい方は以下をお試しください。

 

はじめに用意

試す方法として、まず用意するものが独自の設定画面。

できる限り他の影響を受けないようにする為にシンプルな設定にします。
テーマもプラグインも、WordPressをインストールしたばかりのデフォルトの状態が理想的です。

今回は簡単な設定ページを用意します。

まず設定ページを作成する為に、以下のコードをfunctions.phpなり、追記してください。

function ex_xss_menu() {
  add_menu_page( 'Example Xss' , 'Example Xss' , 'administrator' , 'ex_xss' , 'ex_xss_page' );
}
add_action( 'admin_menu' , 'ex_xss_menu' );

そして管理画面を読み込むと、設定の下のほうに「Example Xss」というメニューが追加されていると思います。(今はそのメニューをクリックしてもエラーが表示されます。まだメニューしか作成していないので。)

 

メニュー設置
メニュー設置

 

次にExample Xssの設定ページ作成用のコードです。先ほどのコードのところに追記してください。

function ex_xss_page() {
  if( !empty( $_POST["ex_text"] ) ) {
    update_option( "ex_text" , stripslashes( $_POST["ex_text"] ) );
  }
  if( !empty( $_POST["ex_area"] ) ) {
    update_option( "ex_area" , stripslashes( $_POST["ex_area"] ) );
  }

  echo '<div class="wrap">';
  echo '<h2>Example XSS Test</h2>';
  echo '<form action="" method="post">';

  echo sprintf( '<p>Input: <input name="ex_text" value="%s" style="width: 300px;" /></p>' , get_option( 'ex_text' ) );
  echo sprintf( '<p>Textarea: <textarea name="ex_area" rows="5" cols="10" style="width: 300px;">%s</textarea></p>' , get_option( 'ex_area' ) );
  echo sprintf( '<p class="submit"><input type="submit" value="%s" class="button button-primary" /></p>' , __( 'Submit' ) );

  echo '</form>';
  echo '</div>';
}

 

これでメニューをクリックすると、シンプルな設定ページが出来上がります。

設定ページ
設定ページ

 

あ、ここは重要です。

Google Chromeのコンソールを表示させておいてください。(キーボードのF12を押す)

コンソール表示
コンソール表示

コンソールで既にエラーが無いことを確認してください。エラーがある場合はそれなりに対処して、エラーが無い状態にしたほうがテストとしては理想的だと思います。

 

で、ここから。

バグとなってしまう症状を確認していきます。

作成した設定ページにinput text 、textareaの2つのフォームフィールドがあります。
どちらでもかまいませんが、何か、フォームを作成するように<form>タグを入力してください。サンプルとして私は以下のように入力しました。

フォーム入力例
フォーム入力例

で、送信ボタンを押して下さい。すると…なんと。

ちゃんとデータは保存されています。変な期待をした方ごめんなさい。 😳

でも、これは一回目の送信ボタンを押した時(一回目のデータ保存時)の動作です。

 

送信ボタンを押した直後、他のページに移動せずGoogle Chromeのコンソールを確認してみてください。

コンソール画面
コンソール画面

 

先ほどは何もエラーは表示されていませんでしたが、送信ボタンを押した直後にエラーが表示されます。しかし、入力したデータはきちんと保存されています

The XSS Auditor refused to execute a script in 'http://example.com/wp-admin/admin.php?page=ex_xss' because its source code was found within the request. The auditor was enabled as the server sent neither an 'X-XSS-Protection' nor 'Content-Security-Policy' header.

なにやらXSSに関してのエラー?注意?が出ていることが分かると思います。

 

ここが大事です。この状態でもう一度「送信」を押してみて下さい。

 

出ましたか?真っ白な何もない画面。アドレスバーにはabout:blank。 😯

Google Chrome だけ、このような仕様だとは全く予想していませんでした。

 

具体的には

あのコンソールの文章をもとに色々なブログを見て回って、私の個人的な考えですが、辺なコードを埋め込まれないようにする為の、Google ChromeのXSS対策処理ではないかと思います。

あ、Firefoxですか?バンバン保存できます。 :mrgreen:
それが良いのか悪いのかは別ですが。

ただ、もっと具体的にどのようなコードを記述すると、このような仕様になるのかを私なりに調べた結果、ひとつは分かりました。(多分、ひとつではなく複数あると思っています)

ちなみにその記述は、

action="

(アクション イコール ダブルクォーテーション)

調べた結果、フォームタグかどうかは関係が無いようでした。

action=”というような記述が問題の現象を引き起こしているようでした。

試してみたい方は、この記述をどこでもいいので記述してみてください。
「新規投稿」や「固定ページ」でも。あのコンソール文が表示されます。
(テキスト編集の場合です)

ただ、ちょっと面白いというか、これは良いのか? 😕 と思ったのですが、

action=''

このように記述すると、何のエラーも出ません。(全角ではありません)

違いは ダブルクォーテーションシングルクォーテーション か。それだけです。

なんでこんな仕様なんでしょうか。ちょっと分かりません。この記述は安全とみなされるのでしょうか。。ちょっとこの仕様に関しては理解が難しいです。  😕

ただ、やっとバグが出るタイミングがはっきりと分かりました。

この仕様が原因でプラグインの設定データが上手く保存出来ない。
という事に気づくまでに、ものすごく時間を取られました。 😳

 

うん、どう対処しようか。。

ダブルクォーテーションを正規表現の置換で強制にシングルクォーテーションに書き換えようか。または「Google chrome、今のところ上手く動きません」と表示させようか。(これは最終案…)

 

解決方法として

私なりに色々と調べた結果、X-XSS-Protection というヘッダーの値を 0 として出力するという事でした。
おそらく、「お願いだからXSSのフィルターチェックしないでね!」というサーバからの意思表示的な意味合いだと私は考えています。

先ほどのエラー文にはこう書いています。

The auditor was enabled as the server sent neither an 'X-XSS-Protection' nor 'Content-Security-Policy' header.

翻訳内容はおそらく、

サーバ管理者(auditor)は’X-XSS-Protection’ または ‘Content-Security-Policy’ヘッダーのどちらかをサーバから送信することができます。

このような意味だと解釈しました。どちらのヘッダーでも良さそうだったので、とりあえず今回は X-XSS-Protection ヘッダーを送信することにしました。両方送信したらどうなるんだろう?

 

じゃあこれをどのタイミングで出力すればいいんだろうと思って色々情報探してみました。

https://gist.github.com/ocean90/3622298

あ、wp_headers なるフックがあるんですね。今回はこの作者のコードをパクって参考に試してみます。 😉

function ex_xss_add_header( $headers, $object ) {
  $headers['X-XSS-Protection'] = 0;
  return $headers;
}
add_filter( 'wp_headers', 'ex_xss_add_header', 10, 2 );

これで無事、action=”を入力しても動作するようになりました。

 

※このコードを使おうと考えた方へ

このままの状態では注意が必要です。

このままだと、アクセスしたページ全てのヘッダーで’X-XSS-Protection’が出力されるので、事前に何かしら条件分岐の処理(管理画面のこの設定画面のこのデータが送信された時等)をしたほうが安全の為に良いと思います。

 

 

Most Plugin Version up for on SSL

いやー、最近かなりバグの報告をいただいていて、なかなかブログの更新ができませんでした。

とにかく相手の症状を理解し、原因探し、解決策考え…。

なんとか最近落ち着きました。いやー、疲れました。
外国の方はあまりWordPress.orgのサポートフォーラムは使わないのでしょうか。。直接のメールの問い合わせが多かったです。

(2~5件/1日、でも英語での問い合わせはかなり大変)

今回もほぼすべてのプラグインをアップデートしました。

 

一部のプラグインは機能追加を加えたりしています。

あと、全てWordPress 3.6 にて動作確認済みです。

 

更新するまでの経緯

ユーザーから寄せられるバグの症状をひとつひとつ見ていっても、なかなか”これだ”という原因が見つからず、よくある「他のプラグインの影響じゃないだろうか」という事を想定して原因を探っていました。

ただここで、症状を報告した方が色々試した結果「あなたのプラグインを使用している場合だけこういう症状が出ます」と調べていただたいた場合はありがたいです。

●●●のプラグインを一緒に使うとバグがありました~とか(バグではないですが)、使っていてこういうバグが出ますよーと教えてくれる場合は、なんとか改善の方法を検討をする事ができますが、「ヘイ、どうなってんだよ。動かないじゃないか」みたいな報告の場合、かなり厳しいです。

どうなっているのか調べたいけど、「動かない」とだけ言われても分からない。
分かるなら、直してますよ~。。 🙁

(そんなことはおいといて)

 

ただ今回、ひつとだけ、いつもと違うバグ報告をいただきました。

そのバグの原因を探る為に、プラグインの影響やテーマ、WordPressのバージョンやPHPの設定等をもとに原因を探っていたのですが、全く見当外れな所でした。。 😳

 

ちなみに、私は英語が得意ではありませんので、バグ報告をいただいた際に、「ごめんなさい、キャプチャを送っていただけませんか?」とお願いすることが時々あります。(というより、最近は画面を見せたほうがスムーズに解決すると感じています)

その際、「自分の画面はなるべく見せたくない」という方が多いと思います。
(特に管理画面に関しては)

なので、プラグインの設定画面”だけ”送ってもらう場合が多いので、その部分だけで原因がどこにあるのかを考えていました。。

これが、原因を探すことができない原因でした。。 😳
どんなに調べても、原因不明。

次に、バグ報告をいただいた方の中に、どでかい画面キャプチャを送っていただけた方がいました。
(操作している画面を動画で送ってくれた方もいました。これは非常に助かりました。  🙂  )

そして、キャプチャと同じように設定したりしてみましたが、相変わらず。。
プラグインの設定画面に原因があると思いひたすら探っていましたが、やはり見つからず。

う~ん。分からない。(こういう時はノートPCを( ̄  ̄ )ノ⌒)

一息ついて、ふと、キャプチャを再度よく見てみると、アドレスバーにhttps
SSLでの管理画面への接続でした。

あ、確かにSSL環境での動作確認はしていなかったな~。でも、SSLでも使えるとは思うけどな。

とりあえず確かめてみないと分からないものは分からない。という事でxampp内のOpenSSLを利用して試してみました。

そして、やっと原因が分かりました。

 

 

SSLの場合だけ上手く動作しなかった

不正確ですが、一言で言うならこんな感じのバグです。

少しだけ正確に言うと、プラグインでJavascriptを読み込むように設定しています。

そのJavascriptでjQueryのSortableやDropable等を設定しているので、データが変化します。最終的にユーザーが保存するデータは、ソート後のデータです。

そしてそのソートを行う為には、その処理を記述しているJavascriptファイルが読み込み出来なければ、もちろんソートすらも出来ません。

そしてSSLの場合だけそのJavascriptファイルが読み込まれない、というバグでした。

ただ、よく分からないのが何故SSLの環境だけ?と思いました。

以前からSSLの環境でプラグインを使っている方はおそらくいると思うのですが、なぜ今になってそんなバグ…?ユーザーが増えてきたから?

と色々考えても仕方ないので、実際に試してみたら…動きました…。SSLでも。  😕

う~ん、やっぱりSSLでも動くよね。。

その後も色々試行錯誤&バグ報告くれた方と色々とやりとりをしていて、やっと分かりました。

正確に言うと、「管理画面へのアクセスはSSL、フロントへは通常のhttp」。

「アクセス」。なるほど。気づきませんでした。 🙄

 

今回の方はおそらくですが、「サイトのURL(home)及びWordPressのURL(siteurl)httpでも管理画面にアクセスするときだけはhttps」という環境で使っているのではないかと考えました。

 

SSLで試すという事で、私のほうでは動作確認の為に、WordPressのインストールからサイト及び管理画面の閲覧まで全てhttpsの環境で全て試していました。

おそらくこのバグ報告をくれた方は、インストール時はhttpでインストールしたのか、または途中でhttpに変更したのか。等をおこなったのではないかと思います。

何をしたのかはおいといて、そのような環境を想定して試してみることに。

そりゃ、いくらget_option(‘siteurl’)とかを使って、CSSファイルやJavascriptファイルを読み込もうとしても、アクセスしているプロトコルはhttpsだったら読み込めないよね。 😎

 

で、私の場合プラグインディレクトリ内のCSSやJavascriptファイルを読み込む際に、

WP_PLUGIN_URL . '/' . dirname( plugin_basename( __FILE__ ) ) . '/'

という風にプラグインディレクトリのURLパスを取得していました。

この  WP_PLUGIN_URL が https:// で管理画面にアクセスした場合、https://にならずにhttp://のままでした。

get_option('siteurl') . '/wp-content' . '/plugins'

このように取得・設定している為です。

get_option(‘siteurl’)では、いくらhttpsでアクセスしても、一般設定にて設定されているhttpでのURLを取得するのでダメでした。

 

ただ私は詳しくは分かりませんが、分かることとしては、Google Chromeの場合、https:// で呼ばれる画面から http:// のファイルにアクセスさせない仕組みのような感じでした。(具体的には何の影響でそうなるのかは分かりません。セキュリティ的な意味合いな気はしますが)

なので、全プラグイン、WP_PLUGIN_URL は使わず、

plugin_dir_url(__FILE__)

これにしました。

これだと、set_url_scheme が間に入って、WP_PLUGIN_URLで取得したアドレスに対してhttpやhttpsも書き換えてくれるような感じでした。というか、こっちで動くならこっちのほうがいいですね。シンプル。 😮

(なんか、WordPressのデバッグ機能を有効にしたら「この関数よりこっちの関数のほうがいいですよ」みたいな表示を出してくれたら嬉しいな。。便利な関数があったとしても、存在が分かりません。。) 

 

で、これで試してもらうと無事動作したようでした。

今回のケースはたまたまどでかいキャプチャと、動画ファイルをいただけたのでなんとか解決までに至りました。

めでたしめでたし  🙂