← Back to Feed CACHED · 2026-10-06 13:47:21 · CACHE_KEY CVE-2026-1207
CVE-2026-1207 · CWE-89 · Disclosed 2026-02-03

Django PostGIS RasterField Band Index SQL Injection

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A niche surveyor's tool left its back door unlocked, and now burglars are testing the handle

CVE-2026-1207 is a SQL injection in Django's django.contrib.gis module, specifically in raster lookups on RasterField — a feature implemented only on PostgreSQL with the PostGIS extension. When an application passes user-controlled input as the *band index* parameter in a raster lookup, Django inlines that value directly into the SQL string instead of binding it as a parameterized value. An attacker who controls the band index can inject arbitrary SQL — demonstrated with pg_sleep() for time-based blind extraction, but extendable to full data exfiltration or modification. Affected versions: Django 6.0a1 – 6.0.1, 5.2a1 – 5.2.10, 4.2a1 – 4.2.27; unsupported series (5.0.x, 5.1.x, 4.1.x, 4.0.x, 3.2.x) are also vulnerable. Patches shipped February 3, 2026 in 6.0.2, 5.2.11, and 4.2.28. HeroDevs also backported a fix to 3.2.27 for their NES customers.

The vendor-provided NVD CVSS of 5.4 MEDIUM (C:L/I:L/A:N) significantly understates the real impact. Django's own security team rated this HIGH and the GHSA advisory scored it 8.1 under CVSS v4. The NVD's C:L/I:L is inaccurate for a demonstrated arbitrary-SQL-execution primitive — the PoC shows pg_sleep(), which means the attacker has broken out of the intended query entirely and can execute *any* SQL the database user is authorized to run, including COPY, pg_read_file, or stacked queries depending on PostgreSQL configuration. Meanwhile, CrowdSec confirmed active exploitation in the wild since February 26, 2026, with steady week-over-week probing volumes targeting ?band= endpoints. The narrow blast radius (PostGIS + RasterField + user-controlled band index) is the only thing keeping this from CRITICAL.

"Active SQLi in Django GIS niche demands upgrade despite narrow attack surface"
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify a Django + PostGIS target

The attacker fingerprints the target as a Django application (via X-Frame-Options: DENY default header, admin panel, or error page leakage) and determines PostGIS is in use. Shodan/FOFA dorks for Django GIS apps or reconnaissance of geospatial API endpoints (e.g., /api/raster/, /map/tiles/) reveals candidates. CrowdSec telemetry confirms attackers are already performing this targeted reconnaissance at scale.
Conditions required:
  • Target application is Internet-reachable or attacker has internal network access
  • Django application uses PostGIS backend with RasterField
Where this breaks in practice:
  • The vast majority of Django deployments do not use PostGIS at all — estimated <5% of all Django apps
  • Among PostGIS users, RasterField is a niche feature for satellite/elevation/terrain data — further narrows to perhaps <1% of all Django installs
Detection/coverage: Wappalyzer, httpx, or custom fingerprinting scripts. CrowdSec shared intelligence feeds flag scanning IPs.
STEP 02

Locate an endpoint exposing the band index parameter

The attacker enumerates API endpoints and query parameters looking for raster-related functionality. Typical targets include /?band=, /api/raster/search/?band=, or any GIS endpoint that accepts a band selector. The attacker needs a code path where user input flows into the band argument of a RasterField .filter() call without integer validation.
Conditions required:
  • Application exposes a URL parameter, form field, or API argument that maps to the band index
  • Application does not validate band index as a strict integer before passing it to the ORM
Where this breaks in practice:
  • Many GIS applications hardcode band index or derive it from server-side logic, never exposing it to user input
  • Applications with input validation (even basic int() casting) are immune
Detection/coverage: WAF rules flagging SQLi payloads in query parameters. Nuclei template CVE-2026-1207.yaml from ProjectDiscovery detects vulnerable endpoints.
STEP 03

Authenticate (if required)

The NVD CVSS vector specifies PR:L (low privileges required), meaning the attacker needs at least basic application-level authentication. However, the GHSA CVSSv4 rates this PR:N, and whether auth is required depends entirely on the application's URL routing — some GIS tile/raster endpoints are public. The attacker registers an account, uses stolen credentials, or targets an unauthenticated endpoint.
Conditions required:
  • If PR:L applies: attacker has or obtains a low-privilege account on the Django application
Where this breaks in practice:
  • Applications behind SSO/MFA raise the bar for credential acquisition
  • Public-facing GIS tile endpoints may bypass this friction entirely
Detection/coverage: Failed login monitoring, anomalous account creation patterns.
STEP 04

Inject SQL via band index

The attacker crafts a request like ?band=1);select+'1'||pg_sleep(3)-- to confirm injection via time delay. Tools: sqlmap with custom injection point (sqlmap -u 'http://target/search?band=1*' --dbms=PostgreSQL --technique=T), or the sw0rd1ight/CVE-2026-1207 PoC directly. The injected SQL executes in the context of the PostgreSQL user Django connects as — typically with full read/write access to the application database.
Conditions required:
  • Steps 1-3 satisfied
  • PostgreSQL accepts the injected syntax (standard for PostGIS setups)
Where this breaks in practice:
  • Database connection user with restricted permissions limits blast radius
  • PostgreSQL log_statement = 'all' creates audit trail
Detection/coverage: WAF SQL injection signatures on band parameter. Database query logging for anomalous pg_sleep, UNION SELECT, or stacked queries. IDS/IPS rules for time-based SQLi patterns.
STEP 05

Exfiltrate data or escalate

Using time-based blind or error-based extraction (sqlmap automates this), the attacker dumps database tables — user credentials, session tokens, PII, or application secrets stored in the database. If the PostgreSQL user has SUPERUSER or pg_read_server_files role, the attacker can read files from the server filesystem (pg_read_file()). Extracted credentials may enable lateral movement if password reuse exists.
Conditions required:
  • Successful SQL injection from step 4
  • Database contains valuable data or credentials
Where this breaks in practice:
  • Time-based blind extraction is slow — full table dumps take hours and generate detectable traffic
  • Properly segmented database with no credential reuse limits lateral movement
  • Column-level encryption on sensitive fields defeats bulk extraction
Detection/coverage: Database activity monitoring (DAM) alerts on bulk SELECT across tables. Anomalous query volume and duration spikes. Network DLP for large outbound data transfers.
03 · Compensating Control

1
HIGH 7.5→LOW 2.0
SEVERITY REDUCED
Validate band index as a strict integer at the application layer — Add input validation that casts the band parameter to int() or rejects non-integer values *before* it reaches the Django ORM. This is the single most effective compensating control because it breaks the injection primitive entirely — no valid integer can contain SQL syntax. Deploy within 30 days per the noisgate mitigation SLA for HIGH severity. Example: band = int(request.GET.get('band', 1)) wrapped in a try/except that returns 400 on failure.
2
HIGH 7.5→MEDIUM 5.0
SEVERITY REDUCED
Deploy WAF rule blocking SQLi in raster query parameters — Configure your WAF (Cloudflare, AWS WAF, ModSecurity, F5 ASM) to inspect and block SQL injection patterns in the band query parameter specifically, and in all query parameters generally. CrowdSec shared blocklists already flag IPs scanning for this CVE. Deploy within 30 days per the noisgate mitigation SLA. Note: WAFs can be bypassed with encoding tricks, so this is defense-in-depth, not a standalone fix.
3
HIGH 7.5→MEDIUM 5.5
SEVERITY REDUCED
Restrict PostgreSQL connection user privileges — Ensure the Django database user has only the minimum required permissions — no SUPERUSER, no pg_read_server_files, no COPY to filesystem. Use row-level security where possible. This does not prevent injection but limits the blast radius to the application's own tables. Deploy within 30 days.
4
HIGH 7.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade Django to patched version — The definitive fix. Upgrade to 6.0.2, 5.2.11, or 4.2.28. For unsupported 3.2.x, either upgrade to a supported series or obtain HeroDevs NES patch (3.2.27). Complete within 180 days per the noisgate remediation SLA for HIGH severity, but given active exploitation, prioritize immediate patching ahead of SLA.
What doesn't work
  • Rate limiting alone — SQL injection needs only one successful request to confirm the vulnerability and begin data extraction. Rate limiting slows bulk extraction but does not prevent the initial compromise or targeted single-query exfiltration.
  • Generic CSRF protection — Django's CSRF middleware protects against cross-site request forgery, not against an attacker directly sending requests to the vulnerable endpoint. CSRF tokens do not validate query parameter content.
  • Switching to a non-PostGIS GeoDjango backend — While technically this removes the vulnerable code path (RasterField is PostGIS-only), it breaks all raster functionality and is not a viable compensating control for applications that depend on raster data.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationConfirmed active. CrowdSec observed first attacks on February 26, 2026 — 23 days after patch release. Steady week-over-week probing volumes targeting ?band= endpoints. Characterized as targeted reconnaissance against PostGIS setups, not mass spraying.
Proof-of-ConceptMultiple PoCs available. sw0rd1ight/CVE-2026-1207 — full Django app demonstrating time-based blind SQLi via pg_sleep(). ProjectDiscovery nuclei template for automated scanning. Listed on pocindex.io with 1 GitHub repo + 1 nuclei template.
EPSS0.12819 (12.8%) — 96th percentile. This places CVE-2026-1207 in the top 4% of all CVEs by predicted exploitation probability, consistent with the confirmed in-the-wild activity.
CISA KEV StatusNot listed as of 2026-10-06. Despite confirmed exploitation, CISA has not added this to the Known Exploited Vulnerabilities catalog.
CVSS Vector AnalysisNVD: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N = 5.4 MEDIUM. GHSA CVSSv4: 8.1 HIGH. Django's own security policy: HIGH. The NVD C:L/I:L rating is arguably inaccurate — demonstrated arbitrary SQL execution should warrant C:H/I:H, yielding ~8.1 under v3.1 as well.
Affected VersionsDjango 6.0a1–6.0.1, 5.2a1–5.2.10, 4.2a1–4.2.27. Unsupported: 5.0.x, 5.1.x, 4.1.x, 4.0.x, 3.2.x (HeroDevs patched 3.2.27 for NES customers).
Fixed Versions6.0.2, 5.2.11, 4.2.28 (released 2026-02-03). Debian: python3-django updated via DSA. Red Hat: patches for Ansible Automation Platform, Satellite, Discovery, Insights. HeroDevs: 3.2.27 for NES.
Scanning / ExposureCrowdSec shared intelligence feeds flag IPs targeting this CVE. No dedicated GreyNoise tag identified. Shodan queries for Django + PostGIS show limited public exposure — most PostGIS-backed Django apps are internal or behind CDN/reverse proxy.
DisclosureReported by Tarek Nakkouch. Disclosed 2026-02-03 via coordinated Django security release. Commits: 81aa529 (4.2.x), 17a1d64 (5.2.x), 8f77e73 (6.0.x).
Red Hat AssessmentClassified as Important. Affects multiple products: Ansible Automation Platform, Satellite, Discovery, Insights — wherever Django is bundled with PostGIS raster capabilities.

Sources.

  1. Django Security Release Announcement
  2. GHSA-mwm9-4648-f68q — GitHub Security Advisory
  3. CrowdSec VulnTracking Report — Active Exploitation
  4. HeroDevs — SQL Injection in Django Raster Lookups
  5. sw0rd1ight/CVE-2026-1207 — PoC Repository
  6. ProjectDiscovery Nuclei Template
  7. SecurityOnline — Django SQLi Exploited in the Wild
  8. Django Commit Fix (4.2.x branch)
05 · The Call

Final Verdict
↑ UPGRADED to HIGH (7.5/10)

Why this verdict

  • Active exploitation overrides narrow surface. CrowdSec confirmed steady in-the-wild probing since February 26, 2026. When attackers are already pulling the trigger, theoretical friction loses weight — the question shifts from 'could someone exploit this?' to 'are they hitting *your* deployment?'
  • NVD CVSS dramatically underscores impact. The 5.4 rating uses C:L/I:L, but the demonstrated PoC achieves arbitrary SQL execution (pg_sleep, stacked queries). A correct assessment of the injection primitive is C:H/I:H, which yields ~8.1 under CVSS v3.1 — aligning with Django's own HIGH rating and the GHSA v4 score of 8.1.
  • Public PoC + nuclei template = low attacker skill floor. sw0rd1ight's full Django app PoC and ProjectDiscovery's nuclei scanner template mean any script kiddie with nuclei -t CVE-2026-1207.yaml can find and exploit vulnerable targets. Combined with sqlmap for automated extraction, weaponization is trivial.
  • EPSS 96th percentile validates real-world signal. At 12.8%, EPSS ranks this in the top 4% of all CVEs — a strong independent indicator that the exploitation characteristics (network vector, low complexity, public PoC) make this a practical attack, not a theoretical one.
  • Role multiplier: production database tier. Django+PostGIS deployments with RasterField are typically geospatial analysis platforms — government mapping, environmental monitoring, defense/intelligence GIS, precision agriculture. These often sit on production PostgreSQL databases containing regulated or sensitive geospatial data (property records, critical infrastructure locations, classified imagery metadata). The blast radius on a successful SQLi is full database read/write, which in these contexts means sensitive data exfiltration. However, this is database-level impact, not fleet/domain/supply-chain scale. Django is NOT a canonically high-value-role component (not an IdP, hypervisor, CA, PAM system, etc.), and the PostGIS+RasterField subset represents well under 1% of all Django deployments, so the HIGH floor (domain/fleet compromise) does not mechanically trigger. The upgrade to HIGH is driven by active exploitation + understated CVSS, not by the role-multiplier floor.
  • Friction acknowledged but insufficient for MEDIUM. Yes, PostGIS + RasterField + user-controlled band index is a narrow surface. But: (1) attackers are already scanning for exactly this combination, (2) the organizations *most likely* to use PostGIS RasterField are high-value targets (government, defense, critical infrastructure), and (3) a single successful SQLi on one of these targets justifies the upgrade.

Why not higher?

Not CRITICAL because the exploitable surface is extremely narrow — the chain requires PostGIS backend + RasterField model + user-controlled band index flowing into ORM lookups without validation. This is likely <1% of all Django installations. Django is a general-purpose web framework, not a canonically high-value-role component (it is not an identity provider, hypervisor, certificate authority, or domain controller). Even when the chain succeeds, the blast radius is database-level (read/write the app's PostgreSQL instance), not fleet-scale or domain-scale. There is no evidence of wormable propagation or supply-chain impact.

Why not lower?

Not MEDIUM because active in-the-wild exploitation is confirmed by CrowdSec with steady week-over-week volumes since February 2026. The NVD CVSS of 5.4 is provably inaccurate — the PoC demonstrates arbitrary SQL execution, not the limited C:L/I:L the vector claims. EPSS at the 96th percentile independently corroborates high exploitability. Public PoCs and a nuclei template make weaponization trivial. The organizations most likely to run PostGIS RasterField (government GIS, defense, critical infrastructure) are high-value targets where SQLi has outsized consequences.

06 · Verification

Crowdsourced verification payload.

Run this script on each host or in each virtualenv/container where Django is installed. Invoke with python3 cve_2026_1207_check.py. No special privileges required — it only reads the installed Django package version. For fleet-wide checks, push via your config management tool (Ansible, Salt, Chef) or scan container images with pip show django in CI.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
"""CVE-2026-1207 version checker — Django PostGIS RasterField SQL Injection.
Outputs: VULNERABLE | PATCHED | UNKNOWN
Exit codes: 1 = vulnerable, 0 = patched, 2 = unknown
"""
import sys
import subprocess
import re

def get_django_version():
    """Try import first, fall back to pip show."""
    try:
        import django
        return django.get_version()
    except ImportError:
        pass
    try:
        out = subprocess.check_output(
            [sys.executable, "-m", "pip", "show", "django"],
            stderr=subprocess.DEVNULL, text=True
        )
        for line in out.splitlines():
            if line.startswith("Version:"):
                return line.split(":", 1)[1].strip()
    except Exception:
        pass
    return None

def ver(s):
    """Parse version string to comparable tuple."""
    parts = re.match(r"(\d+)\.(\d+)\.?(\d+)?", s)
    if not parts:
        return None
    return (int(parts.group(1)), int(parts.group(2)), int(parts.group(3) or 0))

def check(v):
    """Return True if version is vulnerable."""
    # Fixed: 6.0.2, 5.2.11, 4.2.28
    # Vulnerable ranges:
    #   6.0.0 - 6.0.1
    #   5.2.0 - 5.2.10
    #   4.2.0 - 4.2.27
    #   All unsupported series below 4.2 or between supported series
    if v >= (6, 0, 0) and v < (6, 0, 2):
        return True
    if v >= (5, 2, 0) and v < (5, 2, 11):
        return True
    if v >= (4, 2, 0) and v < (4, 2, 28):
        return True
    # Unsupported series: 5.0.x, 5.1.x assumed vuln (no patch)
    if v >= (5, 0, 0) and v < (5, 2, 0):
        return True
    # 4.0.x, 4.1.x — unsupported, assumed vuln
    if v >= (4, 0, 0) and v < (4, 2, 0):
        return True
    # 3.x and below — unsupported, assumed vuln (unless HeroDevs 3.2.27+)
    if v < (4, 0, 0):
        # HeroDevs patched 3.2.27; check for that
        if v >= (3, 2, 27):
            return False  # HeroDevs NES patch
        return True
    return False

def main():
    version_str = get_django_version()
    if version_str is None:
        print("UNKNOWN - Django not found in this Python environment")
        sys.exit(2)

    v = ver(version_str)
    if v is None:
        print(f"UNKNOWN - Could not parse Django version: {version_str}")
        sys.exit(2)

    if check(v):
        print(f"VULNERABLE - Django {version_str} is affected by CVE-2026-1207")
        print("  Fix: upgrade to 6.0.2, 5.2.11, or 4.2.28+")
        # Additional context: check if PostGIS is even in use
        try:
            from django.conf import settings
            if hasattr(settings, 'DATABASES'):
                for alias, db in settings.DATABASES.items():
                    if 'postgis' in db.get('ENGINE', '').lower():
                        print(f"  WARNING: database '{alias}' uses PostGIS — exploitation is possible")
        except Exception:
            print("  Note: could not inspect DATABASES setting (run within Django context for full check)")
        sys.exit(1)
    else:
        print(f"PATCHED - Django {version_str} is not affected by CVE-2026-1207")
        sys.exit(0)

if __name__ == "__main__":
    main()