wp-adminへのゼロデイ攻撃を防ぐ機能を追加 – IP Geo Block更新告知

2014年12月、NTTコミュニケーションズが「Zero day Attack Protection(仮称)」を開発、今年4月からのサービス開始を発表しましたが、我が WordPress プラグイン IP Geo Block も独自の仕組みにより一部の ゼロデイ攻撃 を防御する機能を追加しましたので、その告知とバグ出しの協力願いをしたいと思います 🙂 。

背景

拙作の「プラグイン作者必読!実例に学ぶ脆弱なWordPressプラグインの作り方、又はwp-adminを守る理由」では、脆弱性を持つプラグインの多くで「重要な処理」を行う際、「権限と nonce の検証」が欠落していることを明らかにしました。またその後も 同様の例 が後を絶ちません。

本来なら WordPress 自体が本質的にセキュアな仕組みで出来ていて、この手の脆弱性を作り込む余地がないのが理想でしょう。一方で、もしそんな作りになっていたら、現在私たちが手にしている自由は無かったのかもしれない、とも思います。

それならということで、補助的に「権限の検証」と「nonce の検証」を埋め込むことで、これらが欠落して起きる CSRFSQLi といった攻撃や LFI などの脆弱性からサイトを守る機能を追加してみました。

理想的には、この機能はプラグインに依存せず「ゼロデイ攻撃の防御」が可能です。

が、現実にはそれほど素晴らしいものではなく、防御できるのは一部になります。まずはその辺りの事情から説明させて下さい。

プラグインで「何か重要なことをする」3つの方法

プラグインが 管理メニュー にその入口を追加すると、その URL は、登録した種類に応じて例えば次の様になっているハズです。

  • wp-admin/admin.php?page=my-plugin
  • wp-admin/tools.php?page=my-plugin
  • wp-admin/option-general.php?page=my-plugin

さて、それぞれのページで「ボタンを押した時に何か重要な処理をする」には、(コア系の処理を除けば)最もベーシックな方法として次の3つがあります。

「重要な処理」を実行する3つの方法を実装したサンプル

「重要な処理」を実行する3つの方法を実装したサンプル

wp-admin/admin.php?action=do-my-action

Akismet でも使われている方法 で、ユニークなアクション名 do-my-action を軸に「重要な処理」を実行するコードを組み立てます。

まずは、フォームによるボタンの作成です。

<form method="post" action="<?php echo admin_url( 'admin.php' ); ?>">
    <?php wp_nonce_field( 'do-my-action', '_wpnonce' ); ?>
    <input type="hidden" name="action" value="<?php echo 'do-my-action'; ?>" />
    <input type="submit" value="重要な処理" class="button button-primary" />
</form>

あるいは、a タグで飛ばすなら次の様にします。

<?php
$link = add_query_arg(
    array(
        'action' => 'do-my-action',
        '_wpnonce' => wp_create_nonce( 'do-my-action' ),
    ),
    admin_url( 'admin.php' )
);
?>
<a class="button-secondary" href="<?php echo esc_url( $link ); ?>">重要な処理</a>

またリクエストされたアクション do-my-action をフックするには、以下を定義しておかなければなりません。

add_action( 'admin_action_' . 'do-my-action', 'my_action' );
function my_action() {
    // 権限と nonce の検証
    if ( ! current_user_can( 'manage_options' ) ||
         ! check_admin_referer( 'do-my-action', '_wpnonce' ) ) {
        return; // ログイン画面に戻される
    }

    // ここで重要な処理
    ...

    // 結果の表示:Ajax の場合
    if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
        wp_send_json( $result );
    }

    // 結果の表示:ページを遷移する場合
    else {
        if ( isset( $_REQUEST['_wp_http_referer'] ) ) {
            // wp_nonce_field() が埋め込んだリファラに遷移
            $redirect_to = $_REQUEST['_wp_http_referer'];
        }
        else if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
            // ブラウザが出力したリファラに遷移
            $redirect_to = $_SERVER['HTTP_REFERER'];
        }
        else {
            // 予め設定したページに遷移
            $redirect_to = admin_url( 'admin.php?page=' . 'my-plugin' );
        }

        wp_safe_redirect( $redirect_to );
        exit;
    }
}

余計な経路は介さずに関数 my_action() を起動、「重要な処理」に集中し、「結果の表示」は他に任せるという 潔さ が、この方法のミソでしょう。

wp-admin/admin-ajax.php?action=do-my-action

詳細は 他の記事 に譲りますが、前述の my_action() に Ajax 用のフックを引っ掛けます。

add_action( 'wp_ajax_' . 'do-my-action', 'my_action' ); // ログインユーザー用
add_action( 'wp_ajax_nopriv_' . 'do-my-action', 'my_action' ); // 非ログインユーザー用

おっと、ここでは「重要な処理」に「非ログインユーザー用(no privilege)」は必要ありませんネ。

wp-admin/admin-post.php?action=do-my-action

admin-post.php は、純粋な POST リクエストに対する入口です。Ajax と同様に「ログインユーザー」と「非ログインユーザー」を振り分ける事が出来ます。

add_action( 'admin_post_' . 'do-my-action', 'my_action' );
add_action( 'admin_post_nopriv_' . 'do-my-action', 'my_action' );

ゼロデイ攻撃を防御する仕組みと限界

脆弱なプラグインに代わって「権限と nonce の検証」を行うには、遷移前のページに nonce を埋め込み、「重要な処理」の前に検証を実行しなければなりません。

このため前述の3つの基本形に対し、リンク、フォーム、jQuery による Ajax リクエストの各々に nonce を埋め込み、リクエストを受けたところで検証をする仕組み入れています。

一方、プラグインが「重要な処理」のリクエストを受け付ける方法は、前述の3つ以外にも無数に存在します。例えば admin.php?page=my-plugin&job=do-my-job で、表示の前に処理を振り分けても OK ですし、(酷いのになると、プラグインはイベント駆動で開発されるべきという お約束 を無視して)WordPress を経由しないケースも見受けられます。

同様に、PHP が他の PHP に直接リクエストする場合(サーバーサイド)や、ブラウザから flash や JavaScript のロケーション・オブジェクトを介してリクエストする場合(クライアントサイド)などがあり得ます。このような場合は nonce の埋め込みが出来ず、逆にこの仕組みが邪魔になってしまいます。

そこで、「この仕組みをバイパスする処理」について、および「この仕組みでも防げない脆弱性」について、以下に説明します。

コア系で使われるリクエスト

メディア・ライブラリでは、Plupload が flash や silverlight 経由でアップロードを行なうため、これらのアクション名を特定し、バイパスしています。

他のflash系を使うプラグインなど

jQuery 以外で Ajax を行うプラグイン向けに、バイパスすべきアクションを追加するフィルター・フックを用意しました。

add_filter( 'ip-geo-block-admin-actions', 'my_admin_actions' );
function my_admin_actions( $admin_actions ) {
    $actions = array(
        'do-some-plugin-action', // バイパスするアクション名
    );
    return $admin_actions + $actions;
}

権限昇格(PE)は防げない

WordPress ではバックエンドでの処理に際し、ユーザーの種類と権限 を細かく制限する事が出来ますが、権限の十分条件はプラグインやテーマに依存し、PHP ソースでも解析しない限り外側からは特定する事が出来ません。

そこで本プラグインでは、「ログイン」を最低限の権限としています。

よって、異なる権限間で発生し得る nonce の漏洩や権限の回避といった、「権限昇格」の脆弱性に対する攻撃は防ぐ事が出来ません。

効果の検証

最近発表された脆弱性の事例から、防御が成功する場合と失敗する場合を1例ずつ紹介したいと思います。

防御が成功する場合

2015年1月、All In One WP Security & Firewall の CSRF 脆弱性が CVE に上がりましたJVN へは2015年3月)。

「悪意あるリンクを踏むと、特定の 404 イベント・ログが削除される」という、深刻度の低い脆弱性です 😉 。

残念ながら(?)公式の SVN リポジトリ からは既に該当のバージョンは削除されていますので、ミラーサイトの Github からクローンを落としての再現です。

「悪意あるリンク」は、図の通りです。

404 Event Logs

404 Event Logs

これを WordPress 以外のページに貼付けてクリックすると、All in one Security の管理画面に飛んで、ログが削除されます。

All In One WP SecurityのCSRF

All In One WP SecurityのCSRF

一方、IP Geo Block の「ゼロデイ攻撃を防御」を有効にすれば、以下の様にブロックすることが出来ます。

ブロックされたリクエスト

ブロックされたリクエスト

防御が失敗する場合

2015年3月11日には、WordPress SEO by Yoast1.7.3.3 以下で任意の SQL が実行できる という、深刻度の高い脆弱性が公開されました。

この脆弱性の根本原因は nonce の検証漏れによる CSRF なので、本プラグインにとって最も「オイシイ」ケースですが、残念ながら防御対象であるリクエストの「3つの方法」に当てはまらず、防御できません。

何処まで基本形を拡張できるかは、今後の課題ですネ。

IP Geo Block の設定

「設定」画面の「検証の設定」で、「管理領域」および「管理領域のajax/post」に「ゼロデイ攻撃の防御」を選んで下さい。

IP Geo Blockの設定

IP Geo Blockの設定

オマケの機能

折角組み込んだ nonce が第三者に漏れては元も子もありません。これが起きるケースの1つに、「URL に埋め込まれた nonce がリファラを介して外部に漏れる」が想定できます。

そこでログイン中に外部リンクを踏んだ場合、ブラウザがリファラをリセットしてから飛ぶ仕掛け「リファラ・サイレンサー」を入れてあります。ネットスケープ時代からブラウザが独自に取り入れている機能なため、大抵のブラウザで働くと思いますョ 😉 。

バグ出し協力のお願い

さてさて、今回追加した機能がウマく働くかどうかは、ひとえに「nonce が埋め込めるかどうか」にかかっています。が、ウマくいかない場合も多々あるかと思います。

そこで「試してもイイよ」というパイオニア精神が旺盛な方、トラブル発見の際は、ぜひ サポート・フォーラム とか、コメント欄とかに書き込みをお願いします m_(..)_m

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

海外からの投稿は受け付けていません