[wp]親OWASP ZAPなContactForm7構築案

一通りやって区切りがついたのでまとめ。

環境、前提

セキュリティ基準はZAP。とはいえZAP純正じゃなくてZAPに準拠しつつ色々オミットしたサービスでチェックしてるので甘いところはあるかもしれない。

WEBサーバーはレンタルサーバー。お名前とかロリポップとか。だから、サーバー自体の設定やPHPの深いところの設定はできないっていうね。そういう感じで。

CSRFをやる

functions.phpに以下を追加すれば、サイト内のCF7すべてに対応できる。

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;
        }
    }
}

nameを「CSRFToken」で作ってるのがポイント。任意のものでもいいけどチェッカー側が判別できないとエラー扱いになるのがめんどい。

単発記事はこれ。

やりたいこと自体はCF7のnonceを使って実現できてんだけどね。こちらの環境では解消に至らなかった。それに、nonceの使用は構築によっては微妙だったりするのであんまりアテにできない。

add_filter( 'wpcf7_verify_nonce', '__return_true' );

スパム対策

reCAPTCHAは手軽で手堅いのはいいのだけど、WEBサイトに負荷がかかる原因になったりするので地味にイマイチ。それでもって外部ドメインによる機能だから「Cross-Domain JavaScript Source File Inclusion」の対象になる。

単純にスパムを防げたらいいよね、ってことならreCAPTCHAに固執する必要はなかったりする。

前提は日本語サイトになるけど、以下の考え方でバリデーションを作る。

スパムメールは自由記入欄に半角英数記号を入れ込んでくるから、日本語じゃないとダメな必須項目を作れば弾ける。日本語だと範囲が広すぎるんで、名前のふりがな(フリガナ)欄を作ればひらがな(カタカナ)以外が入ってたらダメっていうバリデーションが組める。

なので、こうなる。

add_filter('wpcf7_validate_text', 'wpcf7_validation_text_furigana', 10, 2);
add_filter('wpcf7_validate_text*', 'wpcf7_validation_text_furigana', 10, 2);
function wpcf7_validation_text_furigana($result, $tag){
    $name = $tag['name'];
    if(false !== strpos($name, 'furigana')) { //nameに「furigana」が含まれるものを対象
        $value = (isset($_POST[$name])) ? (string) $_POST[$name] : '';
        if ($value !== '' && !preg_match('/[ァ-ン]/u', $value)) {  //カタカナ縛り
            $result['valid'] = false;
            $result['reason'] = array($name => 'エラー / この内容は送信できません。');
        }
    }
    return $result;
}

全部の入力欄を対象とすると詰むんで、特定の入力欄を指定するためにフォームなりコードなりの調整が必要になる。

単発記事はこれ。

CSPのインライン対策

CF7には一部インラインの記述がある。CSP的にはアウトで、許可するか修正する必要がある。’unsafe-inline’はベストじゃないので、この場合は修正するのが好ましい。

contact-form-7/includes/contact-form.phpの718行目をコメントアウトするなりして書き換える。

		//return '<div style="display: none;">' . "\n" . $content . '</div>' . "\n";
		return '<div>' . "\n" . $content . '</div>' . "\n";

ただ、プラグインを直接修正するのでアップデートしたら吹っ飛ぶ。毎回修正しなきゃいけないのがダルいからfunctions.phpで置換するのがいい。

add_action('after_setup_theme', function(){
    if(is_admin()) return;
    ob_start(function ($buffer_html) {
        if(is_page('form')){
            $buffer_html = str_replace('<div style="display: none;">', '<div>', $buffer_html);
        }
        return $buffer_html;
    });
}, 10000);

中身がinput:hiddenだからね、インライン要らないんですよ。

単発記事はこれ。

そんな感じで

セキュリティに手を出すと非常に面倒くさいので、振られても拒否するのが基本いいです。

餅は餅屋。

学んだところでそのまま使える手段はほとんどなくて、ケースバイケースで向き合わないといけないから時間をめっちゃ食ってしまうし、慣れとかがないと完了できるか怪しいにもほどがあるし、そもそもWEBサイトの環境により変更できない部分も出てきて詰む可能性がめっちゃある。

後追いで気がつくこと、見つかることばっかりだから、初心者が手を出すのは良くないですねまじで。

本気でセキュリティをやっていくならサーバーの乗り換えから見ていかなきゃいけないんですよ。

無理なんですよ。

お金を積んで専門業者に頼むのがいいです。際限ないし、だるいです。

コメント

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