見出し画像

nonceで防いだ。でも、本当に安全?

この記事は以下の動画の解説記事です。


前回は、nonceなしのGETパラメータを使ったCSRF攻撃のデモを見ました。管理者がリンクを踏むだけで設定が書き換わる、というものです。

今回は修正版プラグインのコードを確認して、実際に攻撃がブロックされることを検証します。ただ、それだけでは終わりません。「nonceを実装したから安全」と思いきや、別の問題が潜んでいます。

修正版のコードを確認する

修正版のプラグインでは、設定フォームに `wp_nonce_field()` が追加されています。

wp_nonce_field(DEMO_NOTICE_BAR_SECURE_PREFIX . 'submit_notice');

引数に注目してください。プラグインのプレフィックス定数に、submitボタンのname属性(`submit_notice`)を連結しています。これがnonceのアクション名になります。

プレフィックスを使うことで、プラグイン間での名前の衝突を防げます。また、アクション名を別途考える手間も省けます。私がUdemyのWordPressプラグイン開発講座でも紹介している、自己流のパターンです。

サーバーが画面を生成するとき、このトークンも一緒に発行されます。フォームの外からはこのトークンを知る方法がないため、外部から細工したURLで同じリクエストを再現することができなくなります。

wp_nonce_field()を追加

処理側の `process_get()` には `check_admin_referer()` が追加されました。引数には、先ほどと同じ式を渡しています。生成と検証で同じアクション名を使うことが必要です。

chck_admin_referer()を追加
check_admin_referer(DEMO_NOTICE_BAR_SECURE_PREFIX . 'submit_notice');

この関数は、リクエストにnonceが含まれているか、そして有効かを検証します。検証に失敗すると `wp_die()` が呼ばれて処理が止まります。

攻撃がブロックされることを確認

前回と同じ攻撃URLを使ってみます。ブラウザに貼り付けてアクセスすると——WordPressのエラー画面が表示されました。「このページにはアクセスできません」という内容です。`wp_die()` が動いた証拠です。

wp_die()によるエラー

設定ページを確認してみると、前回は即座に書き換わっていた通知バーの内容が変化していません。nonceのないリクエストは完全にブロックされています。

念のため、正規のフォームから操作してみます。設定ページを開いて値を入力して保存すると——こちらは問題なく保存されました。正規のフォームはnonceを持っているので、正常に通ります。

攻撃はブロック、正規操作は通過。意図どおりの動きです。

「これで安全?」という問い

さて、nonceを実装したことでCSRF攻撃は防げました。

では、このプラグインはこれで完全に安全になったでしょうか。

このプラグインは、設定した文字列をフロントエンドに表示します。表示するとき、その文字列をそのままHTMLに書き出しています。保存した値が、HTMLの中に直接出力されるわけです。

ここで試したいことがあります。設定フォームに、こんな値を入力して保存してみます。

<script>alert(`XSS`)</script>

nonceが実装されているので、正規のフォームから保存できます。フロントエンドを開くと——アラートが表示されました。

XSSエラー発生

JavaScriptが実行されています。このサイトを訪れた人全員に、このスクリプトが実行されます。

もう少し踏み込んでみます。別のURLに飛ばすコードを保存してフロントを開くと、別のサイトにリダイレクトされました。

javascriptによりリダイレクトを起こすことも

さらに無限ループのコードを試すと、アラートが止まらなくなりブラウザが操作不能になります。実際の攻撃では、アラートの代わりにクッキー情報の抜き取りや、管理者セッションの乗っ取りコードが入ります。

なぜnonceがあってもスクリプトが動くのか

nonceの検証は、リクエストの正当性を確認する仕組みです。「本人の意図で送られたリクエストか」を見ています。

一方、今回起きたことはXSSです。保存された値が出力されるときの問題です。「保存された文字列を、安全にHTMLとして出力できているか」という、まったく別の問題です。

この2つは独立しています。nonceを実装しても、出力エスケープがなければXSSは防げません。

入口(リクエスト)を守ることと、出口(出力)を守ることは、別々に対処する必要があります。

動画でも同じ内容をデモ形式で解説しています。

まとめと次回予告

今回のポイントです。

  • nonceはリクエストの正当性を守る。出力の安全性とは別の問題

  • 保存された値をそのまま出力すると、スクリプトが実行される(XSS)

  • 入口の守りと出口の守りは、それぞれ別に実装する必要がある

次回のEpisode 02では、このXSSをテーマにします。なぜ起きるのか、どう防ぐのか。サニタイズ・エスケープ・バリデーションの違いも整理しながら、コードで確認していきます。


このシリーズは「壊して学ぶ WordPress Security」です。ローカル環境での安全な検証を通じて、WordPressプラグインの脆弱性と安全な実装の違いを学んでいきます。

いいなと思ったら応援しよう!