[wp]ContactForm7のCSRF対策方法

脳みそ焼ききれるかと思った。

参考

【OWASP ZAP】CSRF対策ページに脆弱性診断
CSRFの対策を行っているサイトに対して、OWASP ZAPを利用して脆弱性診断を行います。そのための設定や、実際に攻撃を行い動作確認を行いました。
https://www.securitydawg.com/changing-contact-form-7-with-the-wpcf7_before_send_mail-hook/
PHPのセッションで使用するCookieをSameSite属性に対応させる
php7.3以降は、setcookie関数・session_set_cookie_paramss関数で指定が可…
Contact Form 7 5.0 | Contact Form 7 [日本語]
Contact Form 7 の Ajax による送信時にユーザー情報を取得する - ほぼWebエンジニアリング
WordPress の Contact Form 7 プラグインは通常のフォーム送信ではなく、Ajax(REST API) の機能で入力内容を送信しており、画面…

やること概要

CSRFは要するに、フォームにワンタイムパスを発行して送信処理時に照合することで他所からのアタックに備えろって内容。

なので、以下の作業になる。

  1. セッションごとに変更されるトークン(文字列)を発行
  2. トークンをセッションに登録する
  3. 隠しフィールドにトークンを設定
  4. フォーム送信前にセッションと隠しフィールドを照合

nonceについて

nonceは使わない。

wpにもcf7にもnonceはあるけど使わない、というか使えない。

やっていく

こう。

functions.phpに貼り付けたら完了。

//CSRF対策
global $CSRFToken;
$CSRFToken = bin2hex(random_bytes(32));
add_action( 'after_setup_theme', 'cf7_session' );
function cf7_session() {
    global $CSRFToken;
    session_set_cookie_params(0, '/; SameSite=strict', '', true, true);
    session_start();
    $_SESSION['CSRFToken'] = $CSRFToken;
    session_write_close();
}
add_filter('wpcf7_form_hidden_fields', 'my_wpcf7_form_hidden_fields');
function my_wpcf7_form_hidden_fields($hidden_fields){
    global $CSRFToken;
    $hidden_fields['CSRFToken'] = $CSRFToken;
    return $hidden_fields;
}
add_action('wpcf7_before_send_mail', 'my_wpcf7_verify_CSRFToken');
function my_wpcf7_verify_CSRFToken($WPCF7_ContactForm){
    $wpcf7 = WPCF7_ContactForm :: get_current() ;
    $submission = WPCF7_Submission :: get_instance() ;
    if ( $submission ){
        if (empty( $posted_data )) return;
        $_token = filter_input(INPUT_POST, 'CSRFToken');
        if(empty($_SESSION['CSRFToken']) || $_token != $_SESSION['CSRFToken'] ){
            die('送信失敗');
        }else{
            return $WPCF7_ContactForm;
        }
    }
}

解説的なもの

これを入れるとサイト内のcf7全部がCSRFに対応するので、個別の作業は必要ないです。

セッションの設定、隠しフィールドの追加で同一のトークン(文字列)を引っ張る必要がある。なのでグローバル変数(global $CSRFToken;)を使用した。今になって思うのは、セッションに登録してるんだからセッションから引っ張って隠しフィールドに流し込めばグローバル変数要らなくね?ってことなんだけど、どうでしょうね。

セッション作成時にSameSiteの設定がないとそれはそれで別のセキュリティに引っかかるので注意。例ではドメイン直下から全部を対象にしてあるけど、フォーム用のディレクトリを分けてある場合はそれを指定したほうが安心感がある。環境に合わせていじる。

照合は見ての通り。

何ら問題なく完了したときの処理は「return $WPCF7_ContactForm;」で、関数の引数に返すのを忘れない。「return;」のみだとフォームが読み込みアイコン出っ放しで固まる。

不可の処理をdie()にしてるけどぶっちゃけ何となくなので正しいかどうかはわからない。だけどまあ、普通に使う分にはこっちの条件に該当しないんで表示がどうなろうが知ったこっちゃないですね。

終わってみれば30行程度のことだけど何日も掛かった。辛かった。

多分この内容をオープンにしないほうが金を稼ぐチャンスになるんだろうけど知らん。知るか。疲れた。

突破した瞬間の話

セキュリティチェックのサービスが悪いってところがある。

コード自体を組み上げてから警告が消えるまでに作業があった。

というのがname=”CSRFToken”のことで、nameが合ってないとそもそも蹴られるZAPをやってる人の記事を読んでたときにリストのスクショがあったのでひょっとしてと思ったらそうだった。

使ってるサービスを見返したけどそんな説明どこにもないんだよね。まじしんどい。

おまけ

ものによっては特定のnameが入ってたら良いみたいなところがある。つまり、トークンのチェック自体はスキャンされてなかったり。サービス次第だと思うけどさ。どうでしょう。

コメント

タイトルとURLをコピーしました