wordpress-plugin-hunt について
wordpress-plugin-huntは、WordPressのセキュリティテストでプラグインを特定し、バージョンを既知のCVEと照合するスキルです。複数の検出方法と検証手順を用いて、許可を得た調査を支援します。
主な機能
- 01REST API名前空間の調査
- 02readme.txtによるバージョン確認
- 03HTMLとJavaScriptの特徴分析
- 04バージョンとCVEの照合
- 05複数の手掛かりによる検証
活用シーン
- 01WordPressと確認済みの対象でプラグインを特定
- 02既知の脆弱性に該当するバージョンを確認
- 03readme.txtがブロックされた場合の代替調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill wordpress-plugin-huntインストール後、Claude Code で/wordpress-plugin-huntを入力して読み込まれたか確認します。
詳細
WordPress プラグイン探索スキル
REST API 名前空間のプローブ、readme.txt によるバージョン検出、HTML/JS ソースの分析を通じて、インストール済みの WordPress プラグインを特定します。発見したバージョンを既知の CVE と照合し、悪用します。WordPress プラグインの脆弱性は、RCE に至る最も信頼性の高い経路の一つです。確認済みの CVE には、Elementor、Slider Revolution、ElementsKit、Gravity Forms、Jetpack、WooCommerce、LiteSpeed Cache が含まれます。
使用するタイミング
wp-mass-reconにより、対象が WordPress であることを確認済みの場合。deep-invadeのフェーズ 3 を実行する場合。- CORS/XMLRPC 以外の悪用経路が必要な場合。
- 対象がプラグインを多数使用する WordPress サイト(eコマース、ページビルダー、フォームなど)の場合。
前提条件
- curl、python3 を備えた
terminal。 - WordPress の対象を確認済みであること(
/wp-json/または/wp-login.phpにアクセス可能)。 - CVE の悪用には、特定の CVE の PoC に関する知識が必要です(
security-arsenalスキルを参照)。
実行方法
# Quick plugin namespace scan (30+ plugins)
TARGET="example.com"
for ns in "revslider/v1" "elementskit/v1" "elementor/v1" "gf/v2" "wc/v3" \
"jetpack/v4" "litespeed/v1" "yoast/v1" "acf/v3" "contact-form-7/v1" \
"solidwp-mail/v1" "wpsl/v1" "redirection/v1" "rankmath/v1"; do
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "https://$TARGET/wp-json/$ns")
[[ "$code" != "404" ]] && echo "FOUND: /wp-json/$ns (HTTP $code)"
done
クイックリファレンス
悪用可能な主なプラグイン(米国の中小企業の対象 — 大規模調査で確認された CVE)
| プラグイン | 脆弱なバージョン | CVE | 影響 | 頻度 |
|---|---|---|---|---|
| Slider Revolution | < 6.6.20 | CVE-2024-2534 | ファイルアップロードによる RCE | ~10% |
| ElementsKit | < 2.9.4 | CVE-2023-6851 | SQLi | ~5% |
| ElementsKit | < 2.9.4 | CVE-2023-6853 | ファイルアップロード(認証不要) | ~5% |
| Gravity Forms | < 2.8.2 | CVE-2024-6115 | PHP オブジェクトインジェクション → 認証バイパス | ~8% |
| Jetpack | < 13.1 | CVE-2024-1782 | SSRF | 28% |
| Elementor | < 3.24.0 | CVE-2024-xxxx | 情報開示 → 認証バイパス | 28% |
| LiteSpeed Cache | < 6.5.0 | CVE-2024-50550 | 権限昇格 | ~15% |
| WooCommerce | N/A(API の露出) | N/A | 注文/顧客データ | 42% |
| Yoast SEO | N/A(サイトマップ列挙) | N/A | 投稿者のメールアドレス漏えい | 42% |
検出方法
| 方法 | 判明する内容 | 信頼性 |
|---|---|---|
| REST 名前空間のプローブ | プラグインの存在とデータ | 高(プラグインが REST ルートを登録している場合) |
| readme.txt のバージョン | 正確なバージョン番号 | 中(多くのサイトで readme.txt がブロックされる) |
| HTML ソースの検索 | プラグインの CSS/JS ハンドル | 中 |
| robots.txt | プラグインが生成したエントリー | 低(プラグインがエントリーを追加する場合のみ) |
/wp-content/plugins/<slug>/ | ディレクトリ一覧またはアセット | 中 |
手順
ステップ 1 — REST 名前空間のブルートフォース(40 以上のプラグイン)
TARGET="$1"
OUTDIR="$OUTDIR/plugins/$TARGET"
mkdir -p "$OUTDIR"
# Comprehensive plugin namespace list
declare -A PLUGIN_NAMESPACES
PLUGIN_NAMESPACES["revslider"]="revslider/v1/slides"
PLUGIN_NAMESPACES["elementskit"]="elementskit/v1/layouts"
PLUGIN_NAMESPACES["elementor"]="elementor/v1/globals"
PLUGIN_NAMESPACES["gravityforms"]="gf/v2/forms"
PLUGIN_NAMESPACES["woocommerce"]="wc/v3/products"
PLUGIN_NAMESPACES["jetpack"]="jetpack/v4/settings"
PLUGIN_NAMESPACES["litespeed"]="litespeed/v1/token"
PLUGIN_NAMESPACES["yoast"]="yoast/v1/indexing"
PLUGIN_NAMESPACES["acf"]="acf/v3/posts"
PLUGIN_NAMESPACES["contactform7"]="contact-form-7/v1/contact-forms"
PLUGIN_NAMESPACES["solidwp"]="solidwp-mail/v1/export"
PLUGIN_NAMESPACES["wpsl"]="wpsl/v1/locations"
PLUGIN_NAMESPACES["redirection"]="redirection/v1/redirect"
PLUGIN_NAMESPACES["rankmath"]="rankmath/v1/getHead"
PLUGIN_NAMESPACES["fusionbuilder"]="fusion-builder/v1/elements"
PLUGIN_NAMESPACES["visualcomposer"]="visualcomposer/v1/posts"
PLUGIN_NAMESPACES["ninjaforms"]="ninja-forms/v1/forms"
PLUGIN_NAMESPACES["wpforms"]="wpforms/v1/forms"
PLUGIN_NAMESPACES["mailchimp"]="mailchimp-for-woocommerce/v1"
PLUGIN_NAMESPACES["automatewoo"]="automatewoo/v1"
PLUGIN_NAMESPACES["give"]="give-api/v1/forms"
PLUGIN_NAMESPACES["buddypress"]="buddypress/v1/members"
PLUGIN_NAMESPACES["learndash"]="ldlms/v1/courses"
PLUGIN_NAMESPACES["restrictcontent"]="rcp/v1/memberships"
PLUGIN_NAMESPACES["eventscalendar"]="tribe/events/v1/events"
PLUGIN_NAMESPACES["woosubscriptions"]="wc/v1/subscriptions"
PLUGIN_NAMESPACES["woomemberships"]="wc/v1/memberships"
PLUGIN_NAMESPACES["wpml"]="wpml/v1/languages"
PLUGIN_NAMESPACES["polylang"]="pll/v1/languages"
PLUGIN_NAMESPACES["translatepress"]="trp/v1/languages"
PLUGIN_NAMESPACES["nextgen"]="nextgen-gallery/v1"
PLUGIN_NAMESPACES["envira"]="envira-gallery/v1"
PLUGIN_NAMESPACES["essentialgrid"]="essential-grid/v1/grids"
PLUGIN_NAMESPACES["thegrid"]="the-grid/v1/grids"
PLUGIN_NAMESPACES["masterslider"]="masterslider/v1/sliders"
PLUGIN_NAMESPACES["smartslider3"]="smart-slider-3/v1/sliders"
PLUGIN_NAMESPACES["metaslider"]="ml-slider/v1/slideshows"
PLUGIN_NAMESPACES["duplicator"]="duplicator/v1"
PLUGIN_NAMESPACES["updraft"]="updraftplus/v1"
PLUGIN_NAMESPACES["backupbuddy"]="backupbuddy/v1"
PLUGIN_NAMESPACES["aioseo"]="aioseo/v1/settings"
PLUGIN_NAMESPACES["seopress"]="seopress/v1/settings"
echo "[*] Probing ${#PLUGIN_NAMESPACES[@]} plugin namespaces on $TARGET..."
echo ""
for plugin in "${!PLUGIN_NAMESPACES[@]}"; do
ns="${PLUGIN_NAMESPACES[$plugin]}"
resp=$(curl -sk --max-time 5 --connect-timeout 5 -o /tmp/plugin_check_$$.tmp -w "%{http_code}" "https://$TARGET/wp-json/$ns" 2>/dev/null)
if [[ "$resp" == "200" ]]; then
size=$(wc -c < /tmp/plugin_check_$$.tmp)
# Check if response is real plugin data, not SPA catch-all
if grep -qiE 'id|slides|forms|products|settings|locations|name' /tmp/plugin_check_$$.tmp 2>/dev/null; then
echo "[PLUGIN] $plugin — REST API active (${size} bytes)"
cp /tmp/plugin_check_$$.tmp "$OUTDIR/${plugin}_rest.json"
fi
elif [[ "$resp" == "401" ]]; then
echo "[PLUGIN] $plugin — requires auth (HTTP 401)"
elif [[ "$resp" == "403" ]]; then
echo "[PLUGIN] $plugin — access forbidden (HTTP 403)"
fi
done
rm -f /tmp/plugin_check_$$.tmp
ステップ 2 — readme.txt によるバージョン検出
TARGET="$1"
OUTDIR="$OUTDIR/plugins/$TARGET"
# Common plugin slugs to check
SLUGS=(
"elementor" "revslider" "js_composer" "wp-rocket" "wordfence"
"woocommerce" "jetpack" "litespeed-cache" "yoast-seo" "advanced-custom-fields"
"contact-form-7" "gravityforms" "ninja-forms" "wpforms-lite"
"all-in-one-wp-migration" "updraftplus" "duplicator" "backupbuddy"
"essential-grid" "smart-slider-3" "masterslider" "metaslider"
"fusion-builder" "visualcomposer" "elementor-pro" "essential-addons-for-elementor-lite"
"redux-framework" "buddypress" "learndash" "give"
"the-events-calendar" "events-manager" "wpml-string-translation"
"mailchimp-for-woocommerce" "automatewoo" "woocommerce-subscriptions"
"woocommerce-memberships" "restrict-content-pro" "easy-digital-downloads"
"rank-math-seo" "seo-by-rank-math" "all-in-one-seo-pack" "wp-seopress"
"monsterinsights" "pixel-caffeine" "facebook-for-woocommerce"
"popup-maker" "optinmonster" "mailpoet" "fluentform"
"wp-fastest-cache" "w3-total-cache" "sg-cachepress" "autoptimize"
"redirection" "better-wp-security" "sucuri-scanner" "wp-cerber"
"solid-security" "solidwp-mail" "post-smtp" "wp-mail-smtp"
)
echo "[*] Checking readme.txt for ${#SLUGS[@]} plugins..."
echo ""
for slug in "${SLUGS[@]}"; do
readme=$(curl -sk --max-time 5 --connect-timeout 5 "https://$TARGET/wp-content/plugins/$slug/readme.txt" 2>/dev/null)
if [[ -n "$readme" ]]; then
version=$(echo "$readme" | grep -i "stable tag:" | sed 's/.*: //' | tr -d '\r' | head -1)
name=$(echo "$readme" | grep -i "=== .* ===" | head -1 | sed 's/=== //;s/ ===//')
if [[ -n "$version" ]]; then
echo "[VERSION] $slug: $version ($name)"
# Check for known vulnerable versions
case "$slug" in
revslider)
[[ "$version" < "6.6.20" ]] && echo " [VULN] Slider Revolution < 6.6.20 — CVE-2024-2534 (RCE)" ;;
elementskit|elementskit-lite)
[[ "$version" < "2.9.4" ]] && echo " [VULN] ElementsKit < 2.9.4 — CVE-2023-6851/6853 (RCE)" ;;
litespeed-cache)
[[ "$version" < "6.5.0" ]] && echo " [VULN] LiteSpeed Cache < 6.5.0 — CVE-2024-50550 (priv esc)" ;;
elementor|elementor-pro)
[[ "$version" < "3.24.0" ]] && echo " [VULN] Elementor < 3.24.0 — info disclosure / auth bypass" ;;
gravityforms)
[[ "$version" < "2.8.2" ]] && echo " [VULN] Gravity Forms < 2.8.2 — CVE-2024-6115 (auth bypass)" ;;
jetpack)
[[ "$version" < "13.1" ]] && echo " [VULN] Jetpack < 13.1 — CVE-2024-1782 (info disc)" ;;
esac
fi
fi
done
ステップ 3 — HTML/JS ソースによるプラグイン検出
TARGET="$1"
echo "[*] Scanning HTML source for plugin fingerprints..."
PAGE=$(curl -sk --max-time 10 --connect-timeout 10 "https://$TARGET/" 2>/dev/null)
# CSS/JS handles
echo "$PAGE" | grep -Eo "(?:/wp-content/plugins/|/wp-content/themes/)[a-zA-Z0-9_-]+" | sort -u | while read -r path; do
plugin=$(echo "$path" | grep -Eo 'plugins/\K[a-zA-Z0-9_-]+|themes/\K[a-zA-Z0-9_-]+')
echo " [SOURCE] $plugin (found in HTML)"
done
# Elementor specific
if echo "$PAGE" | grep -q "elementor-element\|data-elementor-id"; then
echo " [SOURCE] Elementor (page builder in use)"
fi
# WooCommerce specific
if echo "$PAGE" | grep -q "woocommerce\|wc-forward\|add_to_cart_button"; then
echo " [SOURCE] WooCommerce (e-commerce active)"
fi
# WP Rocket
if echo "$PAGE" | grep -q "wpr-minify\|rocket-lazyload"; then
echo " [SOURCE] WP Rocket (caching plugin)"
fi
# Cloudflare
if echo "$PAGE" | grep -q "cloudflare\|cf-browser-verification"; then
echo " [SOURCE] Cloudflare (CDN/WAF detected)"
fi
ステップ 4 — CVE 悪用のクイックリファレンス
脆弱なプラグインのバージョンを確認した場合:
TARGET="$1"
# Slider Revolution CVE-2024-2534 (RCE via file upload)
# Requires: revslider < 6.6.20
# Attack: POST to /wp-json/revslider/v1/upload with ZIP containing PHP
# See: security-arsenal skill for full PoC
# ElementsKit CVE-2023-6853 (RCE via file upload, unauthenticated)
# Requires: elementskit < 2.9.4
# Attack: POST to /wp-json/elementskit/v1/upload with specially crafted file
# See: security-arsenal skill for full PoC
# Gravity Forms CVE-2024-6115 (auth bypass)
# Requires: gravityforms < 2.8.2
# Attack: Unauthenticated access to form entries via REST API
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/gf/v2/forms" 2>/dev/null | python3 -m json.tool | head -20
# LiteSpeed Cache CVE-2024-50550 (privilege escalation)
# Requires: litespeed < 6.5.0
# Attack: Crawler token manipulation to gain admin access
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/litespeed/v1/token" 2>/dev/null
注意点
- REST 名前空間が 200 を返しても、プラグインが存在するとは限りません。 一部のテーマやセキュリティプラグインは、すべての
/wp-json/パスに対して 200 を返します。応答に実際のプラグインデータ(id、name、slugフィールドを含む JSON)があることを確認してください。 - 多くのホストで readme.txt がブロックされています。 WP Engine、Hostinger、Cloudflare は、CDN レベルで
readme.txtをブロックすることがよくあります。その場合は、REST 名前空間または HTML ソース検索を使用してください。 - プラグインのスラッグが独自に設定されている場合があります。 プレミアムプラグインでは、独自のディレクトリ名が使われることがあります。
gravityformsがgravityforms-clientsiteになっている場合もあります。wp-content/plugins/のパスを調べ、HTML ソースから実際のスラッグを確認してください。 - SliderRev の v1 エンドポイントは、6.x で 404 を返すことがあります。 プラグインが有効なまま、Slider Revolution の REST エンドポイントが変更されています。
/wp-json/sliderrevolution/sliders/など、現在の v1 以外のパスをプローブしてください。 - プラグインのバージョン比較にはセマンティックバージョニングが必要です。 Bash の文字列比較(
<)では、10.xと2.xの比較に失敗します。複雑な比較にはsort -Vまたは Python を使用してください。 - Elementor の 500 は手掛かりにすぎません。
/wp-json/elementor/v1/favoritesが情報開示となるのは、応答にスタックトレース、サーバーのパス、またはその他の非公開情報が含まれる場合のみです。
ホスティングプロバイダーのパターン(P-23 — プラグイン検出で重要)
ホスティングプロバイダーによって、プラグイン検出における脆弱性の傾向は異なります。
| ホスト | REST ユーザー | readme.txt | CORS | XMLRPC | 最適なプラグイン検出方法 |
|---|---|---|---|---|---|
| GoDaddy | 通常は公開 | 通常はアクセス可能 | 反映されることが多い | 通常は開放 | readme.txt(最もアクセスしやすい) |
| Cloudflare + WP Engine | 通常はブロック | CDN でブロック | 動作する場合あり | ブロック | HTML ソースの grep + REST 名前空間のブルートフォース |
| Hostinger | 公開 | アクセス可能 | 反映されることが多い | 開放 | readme.txt + REST 名前空間 |
| WP Engine(直接接続) | ブロック(401) | ブロック | 混在 | ブロック | HTML ソースのみ |
| Bluehost | 公開 | アクセス可能 | 反映されることが多い | 開放 | すべての方法が有効 |
| SiteGround | 混在 | アクセス可能なことが多い | 混在 | 混在 | REST 名前空間 + readme.txt |
検証
- 検出したすべてのプラグインは、少なくとも2つの検出方法(REST + readme、または REST + HTML ソース)で確認する必要があります。
- すべての CVE は、プラグインの存在だけでなく、正確なバージョン番号と照合する必要があります。
- CVE の悪用は、影響を実証する PoC(バージョン検出だけでは不可)で検証する必要があります。
- 認証が必要なプラグインの脆弱性については、認証情報を取得する手段(CORS、ブルートフォース、公開登録)を記録する必要があります。
コメント
Markdown 対応