XO Securityの仕組み

WordPress

ブログを追加で立ち上げることになり、ワードプレスのセキュリティプラグインを検討する機会がありました。SiteGuardを今まで通り使っても良かったんですが、.htaccessファイルが汚れるのがなんか嫌だったので他を探そうということになったわけです。あとは噂ですがSiteGuardはSWELLと相性悪いらしいです。

まずは有名なWordfence Securityを入れてみました。ただこれ認証キーが必要だったり、有料版への誘導があったり、、、あとは操作性も微妙だったり機能も結構多いのでやめました。

セキュリティプラグインで必要なのって以下くらいでいいはずなんですよね。

  • ログインページをwp-admin/から変更
  • ログイン試行回数IP制限
  • 日本語CAPTCHA
  • ログイン通知
  • XML-RPC無効化
  • ユーザ名を外部から抜かれないようにすること(必須ではない)

XO Securityはこれを満たすかつシンプルなUIでした。

XO Securityのコードを解析してみる

あとは内部仕様ですね。調査してみます。

ソースコードはここにあります。レッツゴー!!

.htaccessを使わない

使ってみたところ.htaccessは汚れませんでした。いいですねスッキリです。

というか.htaccessを使わないことが売りみたいですね。

プラグイン有効時の挙動

DBにテーブルが1つ追加されるぽいです。

    $sql = "CREATE TABLE {$wpdb->prefix}xo_security_loginlog (
        id bigint(20) unsigned NOT NULL auto_increment,
        success boolean NOT NULL DEFAULT '0',
        login_time datetime NOT NULL default '0000-00-00 00:00:00',
        ip_address varchar(46) default NULL,
        login_type tinyint(1) default '0',
        lang varchar(5) default NULL,
        user_agent varchar(255) default NULL,
        user_name varchar(100) default NULL,
        PRIMARY KEY (id),
        KEY success (success),
        KEY login_time (login_time),
        KEY ip_address (ip_address),
        KEY user_name (user_name)
        ) {$charset_collate};";

ログイン系の処理はこのテーブルで管理されるぽいですね。

REST API無効化

rest_endpoints()ではログインしていないユーザーに対して特定のREST APIエンドポイントの無効化ができます。プラグインとかからは使えるぽい?エンドポイントの指定はGUIの設定からできます。

disable_rest()ではREST APIの1系、2系ともに完全に無効化する処理が書かれてます。無効化設定をオンにしている、且つ上記rest_endpoints()を使用してない場合はREST APIが完全に使えないようにします。

public function rest_endpoints( $endpoints ) {
    if ( ! is_user_logged_in() ) {
        $s = isset( $this->options['rest_disable_endpoints'] ) ? $this->options['rest_disable_endpoints'] : '';
        if ( '' !== $s ) {
            $disable_endpoints = explode( ',', $s );
            foreach ( $disable_endpoints as $disable_endpoint ) {
                foreach ( $endpoints as $key => $value ) {
                    if ( $key === $disable_endpoint ) {
                        unset( $endpoints[ $key ] );
                    }
                }
            }
        }
    }
    return $endpoints;
}

public function disable_rest() {
    remove_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' );
    if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) {
        remove_action( 'wp_head', 'rest_output_link_wp_head', 10 );
    }
    remove_action( 'template_redirect', 'rest_output_link_header', 11, 0 );

    // REST API 1.x.
    add_filter( 'json_enabled', '__return_false' );
    add_filter( 'json_jsonp_enabled', '__return_false' );

    // REST API 2.x.
    add_filter( 'rest_enabled', '__return_false' );
    add_filter( 'rest_jsonp_enabled', '__return_false' );
}

ログイン試行回数制限

ログイン試行回数やIP制限はここで管理されてますね。

private function is_login_ok() {
    global $wpdb;

    $locked = false;

    $ipaddress = $this->get_ipaddress();

    $interval_hour = isset( $this->options['interval'] ) ? (int) $this->options['interval'] : 0;
    if ( $interval_hour > 0 ) {
        $time = gmdate( 'Y-m-d H:i:s', (int) strtotime( current_time( 'mysql' ) ) - ( $interval_hour * 60 * 60 ) );

        $login_time = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT login_time FROM {$wpdb->prefix}xo_security_loginlog WHERE ip_address = %s AND success = 1 ORDER BY login_time DESC LIMIT 1;",
                $ipaddress
            )
        );

        if ( null !== $login_time ) {
            $time = max( $time, $login_time );
        }

        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->prefix}xo_security_loginlog WHERE ip_address = %s AND success = 0 AND login_time > %s;",
                $ipaddress,
                $time
            )
        );

        $limit_count = isset( $this->options['limit_count'] ) ? (int) $this->options['limit_count'] : 1;
        if ( $count >= $limit_count ) {
            $locked = true;
        }
    }

    return ! $locked;
}

CAPTCHA機能

ログインフォームにCAPTCHAを設置できます。

public function login_form() {
    $char_mode = isset( $this->options['login_captcha'] ) ? $this->options['login_captcha'] : 'en';
    $src = XO_SECURITY_URL . '/captcha/captcha.php?prefix=login&char_mode=' . rawurlencode( $char_mode );

    echo '<p><img id="xo-security-captcha" src="' . esc_url( $src ) . '" alt="CAPTCHA" width="100" height="36"></p>';
    echo '<p>';
    echo '<label for="xo_security_captcha">' . esc_html__( 'CAPTCHA Code', 'xo-security' ) . '</label><br />';
    echo '<input type="text" name="xo_security_captcha" id="xo_security_captcha" class="input" value="" size="10" aria-required="true" autocomplete="off" required="required" />';
    echo '</p>' . "\n";
}

XML-RPC無効化

モバイルアプリを使って記事を投稿するわけでもないので余計な脆弱性を残さないためにも無効化していいと思います。Jetpackプラグインを使ってる場合はXML-RPCが必要なので無効化しないよう注意。被リンク目的でピンバッを使ってる方以外は何に使うねんって感じなので無効化推奨。

public function xmlrpc_enabled() {
    $blocked_tarpit = isset( $this->options['blocked_tarpit'] ) ? (int) $this->options['blocked_tarpit'] : 0;
    if ( $blocked_tarpit > 0 ) {
        sleep( $blocked_tarpit );
    }
    return false;
}

public function remove_pingback( $methods ) {
    unset( $methods['pingback.ping'] );
    unset( $methods['pingback.extensions.getPingbacks'] );
    return $methods;
}

ユーザ名の保護

ワードプレスでは外部から簡単にユーザ名を引っ張ってこれちゃうのでそれのマスクを行います。

remove_comment_author_classではコメントのHTMLクラスからユーザー名に関連するクラスを削除します。

public function remove_comment_author_class( $classes ) {
    foreach ( $classes as $key => $class ) {
        if ( strstr( $class, 'comment-author-' ) ) {
            unset( $classes[ $key ] );
        }
    }
    return $classes;
}

author_rewrite_rulesとauthor_rewriteメソッドは、作者アーカイブページへのアクセスを無効にし、代わりに404ページへリダイレクトします。これにより、ユーザー名がURLに表示されることを防ぎます。

public function author_rewrite_rules() {
    return array();
}

public function author_rewrite() {
    if ( is_admin() ) {
        return;
    }

    if ( filter_input( INPUT_GET, 'author' ) ) {
        $this->redirect_404();
        exit;
    }

    if ( isset( $_SERVER['REQUEST_URI'] ) && preg_match( '#/author/.+#', esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ) {
        $this->redirect_404();
        exit;
    }
}

filter_oembed_response_dataメソッドは、oEmbedレスポンスから作者のURLと名前を削除します。

public function filter_oembed_response_data( $data ) {
    unset( $data['author_url'] );
    unset( $data['author_name'] );
    return $data;
}

oEmbedは外部メディアコンテンツ(ビデオ、画像、テキスト、音楽など)を埋め込むためのフォーマットおよびプロトコルです。簡単な例としてはブログでURLリンクを挿入するだけで記事のタイトルやアイキャッチ画像などの情報をプレビュー表示できることですね。

XO Securityはシンプルで良い

結論XO Securityはシンプルで良い、ということで採用です。2023年もしっかりメンテナンスされているのでありがたいです。GUI上では以下のような機能が確認できます。使いやすいので検討してみてはいかがでしょうか。