WordPress での CSRF 対策

nonce ってナンスか?

春だというのに寒いですねぇw

それはさておき、みなさんは nonce という言葉を聞いたことがあるでしょうか? 日本ではあまり馴染みがないように思いますが、コンピュータ (というか暗号) の世界では一度だけ使われる使い捨てのユニークな数 (英語版 Wikipedia の説明) のことを言います。

WordPress には CSRF 対策として nonce という機能が実装されています。 wp_create_nonce で nonce を生成し、wp_verify_nonce でこれを検証することができます。 リンク先の Codex にも例がありますが、wp_create_nonce で生成した nonce を HTML のフォームに埋め込み、サーバーで受けたその値を wp_verify_nonce で検証する、というような使い方ができます。

ただし、注意点が一つあります。 WordPress の nonce は本来の意味の nonce とは異なり、ワンタイムの使い捨てというわけではありません。 12時間ごとに変更され、検証に際しては 24時間有効です。 もっとも CSRF 対策として考えたとき、ワンタイムである必要はありません (例えば高木先生の記事)。 推測されないようにきちんとランダムであれば、そして有効期間が適切であれば CSRF 対策として十分です。

wp_verify_nonce は、現在の nonce の値と同じならば 1、1つ前の nonce と同じならば 2を返します。 つまりデフォルトでは 0-12時間で 1、12-24時間で 2が返されることになります (時間の数え方は後述)。 nonce が一致しなければ wp_verify_nonce は 0を返すので、通常はこれをそのまま真偽判定条件式として使用します。

有効期間の短縮

その有効期間ですが、デフォルトの 24時間を短縮したいときは add_filter することで変えられます。

add_filter( 'nonce_life', 'my_nonce_life' );

function my_nonce_life() {
    return 3600; /* 1 hour */
}

このテクニックはテスト時にも使えそうですね。

なお、この有効期間は wp_create_nonce を呼んだ時からカウントダウンが始まるわけではなく、time() の値を使って周期的に変えているだけなので、ちょうど変わり目 (GMT の 0時とか 12時とか) にあたると短時間で値が変化することになります。 それでも先述の通り一つ前の nonce まで有効と判定されるので通常は問題ないでしょう。

nonce の素

さて、この nonce ですが、以下の情報を元に生成されます。

  1. ユーザー ID
  2. 時刻 (といっても 12時間単位)
  3. wp_create_nonce に渡された引数 $action
  4. サイト毎に持っている秘密キーと Salt

これらが同一であれば同じ値になります。 ここで開発者にとって気になるのは 4. の秘密キーと Salt ではないでしょうか? そして自分の wp-config.php に以下のような記述を見つけて焦るわけです。

define('AUTH_KEY',         'put your unique phrase here');
define('SECURE_AUTH_KEY',  'put your unique phrase here');
define('LOGGED_IN_KEY',    'put your unique phrase here');
define('NONCE_KEY',        'put your unique phrase here');
define('AUTH_SALT',        'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT',   'put your unique phrase here');
define('NONCE_SALT',       'put your unique phrase here');

これを見ると「げっ、ウチのサイト、ヤバい??!」と思ってしまいますよね。

でも、安心してください。 実はこのような wp-config.php となっている場合は、自動的に秘密キーと Salt を生成してデータベースに保存し、それを使ってくれます。 このままでも「put your unique phrase here」をキーとして使うことにはなりません。

となると wp-config.php を書き換えたくなるのは定期的にキーを変更したい場合ぐらいでしょうか。 生成には https://api.wordpress.org/secret-key/1.1/salt/ を使いましょう。

学習のために

興味のある人は wp-include/pluggable.php の nonce 関連関数を読んでみましょう。

私が WordPress の nonce について知ったのは「Professional WordPress Plugin Development」という書籍によってです。 この書籍は WordPress のプラグイン開発に必要な情報が一通りまとまっており、分量も重くないのでお勧めです。 英語も難しくないですし。

英語ついでですが、こちらの WordPress の nonce についての記事も参考情報として挙げておきます。


2013.4.17 追記

nonce の変わり目の際の動作について訂正。(一つ前の値も有効なので変化してもすぐに verify エラーになるわけではない)

WordPress 3.6 と Thin Out Revisions

WordPress 3.6 のβ1がリリースされました。 今回はそれをとっかかりに近況をまとめる感じで書いてみます。

WordPress 3.6 のリビジョン画面

WordPress 3.6 ではリビジョン画面が刷新されます。 下のスクリーンショットを見てください! これまでのリビジョン選択用テーブルの代わりに上部にスライダーが用意され、Ajax でリビジョン選択を行なえるようになります。

WP3.6 revision screen

そして、この画面のプログラミングには Underscore.jsBackbone.js が使用されています。 私は JavaScript 界隈に詳しいわけではないのでアレですが、どちらも人気のあるライブラリのようです。 で、私は Thin Out Revision というリビジョン画面に機能を追加するためのプラグインをリリースしているのですが、jQuery で画面に機能追加するような動作なのでそのままでは WordPress 3.6 で動かないわけです。 「ああ、困ったなあ」と思ったのがβ1 前のα版を触っていた 3月下旬のことでした。

Backbone.js

Backbone.js は MVC フレームワークです。 WordPress 3.6 のリビジョン画面も Model と View を使ってプログラミングしています。 そうすると画面ロード時に jQuery でちょっといじる程度では対応できないのです。 もしそれをやったとしても、画面が動的なのでボタンを押したりスライダーを使うとすぐに効果が消えてしまいます。 ああ、困った。

そこで Backbone.js を学習せねば、ということになるのですが、結局本家マニュアルと WordPress の wp-admin/revision.php と wp-admin/js/revisions.js が一番の材料でした。 その他に役に立ったのは以下のチュートリアルです。

あと、Waviaei さんの記事で紹介されている WordPress 3.5 関連動画で Underscore.js と Backbon.js が結構な時間を使って紹介されていて何ができるかを知るのには良いと思います。 結構早口な英語ですが。 ちなみに私はこの動画を見て JavaScript コンソールを使用するために Chrome をインストールしました。 まだそんなレベルです。

Prototypal inheritance

そして、もろもろ考慮した結果「リビジョン画面を拡張するにはそれ用の View を継承して手を入れるしかないでしょ」との結論に至りました。 現在は以下のようなコードを書いていて、何とか Thin Out Revisions を WordPress 3.6 に対応させることができそうです。

  TOR = {
    View: wp.revisions.view.Diff.extend({
      events: {},

      initialize: function() {
        _.bindAll(this, 'render');
        this.events = _.extend({'click #tor-thin-out': 'thinOut'},
                                wp.revisions.view.Diff.prototype.events);
      },

継承についてやってみてよくわからなかったのはやはり prototype 関連です。 JavaScript はクラスベースではなくプロトタイプベースの言語ということで、ここをしっかり理解せねばなりません。 これについては「JavaScript: The Good Parts」という本を読むのが良いと思います。 (私は自分の記事に従い洋書 Kindle 版を購入しましたが)

この部分についてはネット上にもきちんとまとめられた記事があります。私には今のところ __proto__ は必要なさそうですが。

その他

あと、関連する JavaScript ファイルは非同期に読み込まれているようなので SetTimeout (あるいは _.delay) で実行を遅らせねばなりません。 このあたりは色々な方が書いているのを参考にさせてもらいました。

それと WordPress 3.5 と 3.6 で動作を変えなければなりませんが、これについては PHP の方でクラスを階層化しました。 カレントバージョン用のベースクラスに対し 3.5 用サブクラスで必要な関数をオーバーライドするようにしています。

というわけで色々苦労していますが、JavaScript の経験値を積んでおり、プラグインのバージョンアップを WordPress 3.6 のリリースに間に合わせるのは何とかなりそうな気がしています。 後はもうちょっと利用者が増えたらなあ、という感じですね。