正規表現の否定の先読みで admin-ajax.php を除外しつつ /wp-admin/ 以下のアクセスを拒否する


WordPress と Apache (HTTPD Server) を使う場合、管理領域 /wp-admin/ 以下への悪意あるアクセスから WordPress を守る Apache のアクセス制御の設定を書くことがありますが、 /wp-admin/admin-ajax.php は一般ユーザにもアクセスさせる必要があります。

拒否設定と例外的な許可設定の2個のブロックを並べて書く方法が主流ですが、正規表現の否定の先読みで書くと設定に必要なブロックが1個と簡潔になり便利です。

WordPress の管理領域 /wp-admin/ 以下を守る

まずはよく見かける Satisfy Any による方法を書いてみます。

Satisfy Any を使って拒否設定を上書きする設定を書く方法

WordPress ではブルートフォースアタックなど悪意あるアクセスを遮断するため、管理ツール領域 /wp-admin/ 以下をウェブサーバレベルで一般のアクセスを拒否することが多く行われます。

<LocationMatch "^/wp-admin/">
        AuthType Digest
        AuthName "interest_ae"
        AuthDigestDomain /
        AuthUserFile /var/www/auth/post.digest
        Require valid-user
</LocationMatch>

上記では DIGEST 認証を使用していますが、BASIC 認証や IP アドレスによるアクセス制御をすることもあるでしょう。

注意しないといけないのは、 /wp-admin/ 以下であっても /wp-admin/admin-ajax.php は一般ユーザがアクセス可能でなくてはならない 点でしょう。

上述の /wp-admin/ 一般アクセス拒否と両立するため、改めて /wp-admin/admin-ajax.php は許可をするという設定を書く解説はよく見かけます。

<LocationMatch "^/wp-admin/">
        AuthType Digest
        AuthName "interest_ae"
        AuthDigestDomain /
        AuthUserFile /var/www/auth/post.digest
        Require valid-user
</LocationMatch>

<Location "/wp-admin/admin-ajax.php">
        Satisfy Any
        Order allow,deny
        Allow from all
</Files>

Satisfy ディレクティブ は、Allow ディレクティブRequire ディレクティブ が両方適用される際、どちらかの許可を満たせば許可されるのか (Any)、どちらの許可を満たして初めて許可されるのか (All) を指示するためのディレクティブです。なお Satisfy のデフォルトは All なので、 Satisfy Any は明示的に書く必要があります。

Order ディレクティブAllow ディレクティブ は若干のややこしさがありますが、上記の記載だと「全許可」となります。

日本語で読むと

  • /wp-admin/ 以下へのアクセスを許可(認可)するには DIGEST 認証をクリアする必要がある
  • ただし /wp-admin/admin-ajax.php へのアクセスは、前述の /wp-admin/ への認証に成功するか「全許可」のどちらかを満たす( Satisfy Any )なら許可される
    • 認証の可否はともかく、「全許可」の方は無条件で適用されるので、結果的に /wp-admin/admin-ajax.php は全許可として誰もがアクセス可能となる

といった感じです。

慣れている人ならいいのですが、Satisfy Order Allow あたりは Apache 独特の話なので(これに類似する認証認可の機構は他のソフトウェアにもあるでしょうが)パッと見て分かりづらい印象を覚える人も多いと思います。私も Apache を使って長く経ちますが、未だにパッと見てすぐに読めないことがあります。

正規表現の否定の先読みで /wp-admin/admin-ajax.php を拒否設定から除外する

/wp-admin/ で始まる文字列だけど /wp-admin/admin-ajax.php にはマッチしない」という正規表現が書ければ、LocationMatch ブロックディレクティブ1つで意図した拒否ができそうです。

実はそういう正規表現を書くことができます。これを実現するのが正規表現の 否定の先読み です。

実際に見てみましょう。

<LocationMatch "^/wp-admin/(?!admin-ajax\.php)">
        AuthType Digest
        AuthName "interest_ae"
        AuthDigestDomain /
        AuthUserFile /var/www/auth/post.digest
        Require valid-user
</LocationMatch>

正規表現の (?!☆)否定の先読み (negative lookahead) という書き方で、その位置から先(書字方向が向かう先、日本語を含む大部分の言語では右側)にその正規表現 ☆ が無いことをマッチ条件の一つにするという書き方です。

例えば以下のような例があります

  • 田中(?!さん|さま|様) … 有効な敬称が伴っていない「田中」にマッチする(推敲時に敬称漏れチェックをする場合など)
  • 京都(?!市|府) … 京都市や京都府ではない「京都」にマッチする

なので正規表現 ^/wp-admin/(?!admin-ajax\.php)

  • /wp-admin/ から始まる文字列にはマッチ条件の一つを満たすが
    • しかし /wp-admin/admin-ajax.php と続く文字列にだけはマッチしない
    • それ以外の /wp-admin/ から始まる文字列にはマッチする

という正規表現となります。

否定の先読みってどの環境で使えるの?

否定の先読み、初学者向け書籍で正規表現が取り上げられていたとしても解説されないことが多い、いわば中級者向け機能です。

世間で広く使われる正規表現の構文は、大きく分けて「基本正規表現」「拡張正規表現」「Perl互換正規表現」の3種類あります。 否定の先読みが使える正規表現の構文は、このうち「Perl互換正規表現」と呼ばれるもの です。

名前 英語名 英語略称 使われる場所
基本正規表現 basic regular expression BRE sed や grep の標準
拡張正規表現 extended regular expression ERE sed や grep の拡張モード、find など
Perl互換正規表現 Perl compatible regular expression PCRE Perl、PHP(preg系関数)、Ruby をはじめとしたプログラミング言語、Apache 設定ファイル

このうち Apache は、設定ファイルの正規表現を評価するために、pcre という文字通り PCRE を解釈する正規表現ライブラリを使用しています 。よって否定の先読みが使えます。

突然 Apache の設定ファイルに否定の先読みの正規表現を書くと、組織によっては今後保守する人が読めない懸念があるかもしれません。設定ファイルにこの記事の URL をコメントとして書いておくと、未来に保守する人が助けられるかもしれません。

正規表現は可読性を損なうとして嫌う人もいますが、要所で使うことで今回のように短い正規表現が全体の簡素化に貢献することもあります。正規表現のちょっと進んだ使い方に興味が湧いた方は、巻末の参考文献などからぜひ調べてみて下さい。

参考