← Back to Feed CACHED · 2026-07-10 03:23:45 · CACHE_KEY CVE-2026-1207
CVE-2026-1207 · CWE-89 · Disclosed 2026-02-03

6

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

A SQL injection hiding in a GIS corner of Django that most defenders forgot they shipped

CVE-2026-1207 is a classic string-concatenation SQLi (CWE-89) in Django's GIS raster lookup path. When an application uses RasterField on PostGIS and passes a user-controlled *band index* into a raster lookup, Django inlines that integer into the generated SQL rather than binding it as a parameter. An authenticated user who controls that value can break out of the intended position and inject arbitrary SQL against the backing PostgreSQL/PostGIS database. Fixed versions are 6.0.2, 5.2.11, and 4.2.28, all shipped 2026-02-03; older unsupported branches (3.2.x, 4.1.x, 5.0.x) were not evaluated but are known-vulnerable per HeroDevs, which shipped a backport for 3.2.27.

The vendor's MEDIUM/5.4 rating leans on the PR:L and C:L/I:L scope — authenticated, partial impact. That framing understates two things: (1) SQLi against a production PostGIS instance is *rarely* actually partial — union-based extraction and second-order writes routinely escalate to full schema egress and tampering; (2) CrowdSec confirmed in-the-wild exploitation from 2026-02-26 onward, with sustained week-over-week scanning of /?band= and /api/raster/search/?band= patterns. Vendor severity does not match reality — this belongs in the HIGH bucket for any org actually running GeoDjango + PostGIS.

"Vendor called this MEDIUM. Active exploitation in the wild plus SQLi on a data tier says otherwise — upgrade to HIGH."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Identify a Django + PostGIS target with a RasterField endpoint

Attacker fingerprints Django (X-Frame-Options, admin paths, error signatures) and probes for GIS raster endpoints. Public scanning telemetry from CrowdSec shows probes against /?band=, /api/raster/search/?band=, and similar query strings. Tools in use: httpx, nuclei templates, and custom crawlers looking for map/tile/raster APIs.
Conditions required:
  • Django application reachable by the attacker
  • App uses django.contrib.gis with PostGIS backend
  • At least one view exposes a raster lookup where a band index maps to a request parameter
Where this breaks in practice:
  • GeoDjango RasterField is a niche feature — only apps doing geospatial raster analytics use it
  • Most Django apps route GIS through vector fields, not raster
  • Read-only or cached tile services often bypass the ORM path entirely
Detection/coverage: GreyNoise / CrowdSec tag probes to band= params; Nuclei has community templates; WAF signatures for UNION SELECT in band parameter catch naive payloads.
STEP 02

Obtain a low-privileged authenticated session

CVSS PR:L is not decorative — the vulnerable code paths require an authenticated Django user. The attacker registers (if signup is open), uses stolen creds, or leverages a companion IDOR/auth-bypass. sqlmap with --cookie and --csrf-token is the workhorse here.
Conditions required:
  • Valid session cookie or API token for the target Django app
  • The authenticated role has permission to hit the raster-lookup view
Where this breaks in practice:
  • Enterprise apps behind SSO/MFA raise the cost of session acquisition
  • Rate limiting on the vulnerable endpoint slows blind exfil
  • Some deployments gate GIS features to internal staff only
Detection/coverage: Standard auth anomaly detection; Django audit logs of the authenticated user id firing thousands of raster queries is a bright signal.
STEP 03

Inject via the band index parameter

The attacker submits a crafted band value that closes the integer context and appends SQL — e.g. 1) UNION SELECT current_user, version(), NULL --. Because Django inlines the value instead of binding it, PostGIS executes the appended statement. sqlmap -u ... --technique=U --dbms=postgresql automates enumeration.
Conditions required:
  • Endpoint reaches the vulnerable RasterField lookup with attacker-controlled band index
  • No integer-only allowlist / type coercion sits in front of the lookup
Where this breaks in practice:
  • Many apps wrap band selection in a form or serializer that forces IntegerField validation — that kills the payload before it reaches the ORM
  • PostGIS role separation may deny the app user access to sensitive schemas
Detection/coverage: Postgres log_statement=all catches the injected SQL; WAFs with SQLi signatures on numeric params catch string-typed payloads; Django DEBUG=False still leaks 500s that indicate injection surface.
STEP 04

Enumerate schema and exfiltrate

Union-based extraction of information_schema.tables, pg_user, application session tables, and password hashes. On a chatty error page or via time-based blind, the attacker can pivot to pg_read_server_files if the DB role permits, or to writing rows if the app user has INSERT/UPDATE. Practical tooling: sqlmap --dump-all, custom UNION harnesses.
Conditions required:
  • DB user has SELECT on target tables (typical for the Django app role)
  • Attacker can iterate requests without lockout
Where this breaks in practice:
  • Least-privilege DB roles (no pg_read_server_files, no COPY) cap severity to the app's own schema
  • Row-level security policies further constrain what the injected query can read
Detection/coverage: Anomalous query volume from the app's DB user; unusual information_schema reads; DAM tools (Imperva, DataDog DB Monitoring) flag.
STEP 05

Pivot to auth-token / credential abuse

Extracted session tokens, django_session rows, or Argon2/PBKDF2 password hashes feed offline cracking and account takeover. If the app stores API keys or OAuth refresh tokens, the attacker escalates laterally to whatever those keys unlock — the SQLi itself doesn't need code exec if it hands you the keys to the kingdom.
Conditions required:
  • Sensitive material stored in the reachable schema
Where this breaks in practice:
  • Hashed passwords with modern KDFs resist cracking
  • Secrets stored in a vault (not the DB) neutralize this pivot
Detection/coverage: Impossible-travel / new-device sign-ins on cracked accounts; secrets-manager audit logs if keys are rotated after breach.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusActive exploitation confirmed by CrowdSec starting 2026-02-26; steady week-over-week probing, characterized as *targeted* rather than opportunistic spray
KEV statusNot KEV-listed as of 2026-07-10; CrowdSec and multiple trackers expect addition
EPSS0.09436 (~9.4%) — elevated for a MEDIUM-labeled CVE; consistent with observed exploitation
Proof-of-conceptNo public weaponized PoC in mainstream repos as of writing; sqlmap handles this class trivially once the endpoint is known. Attack traffic patterns published by CrowdSec
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N = 5.4 MEDIUM — vendor rates impact as *partial*; real-world SQLi impact is typically full DB read
Affected versionsDjango < 4.2.28, 5.2 < 5.2.11, 6.0 < 6.0.2; unsupported 3.2.x, 4.1.x, 5.0.x also vulnerable (HeroDevs confirmed 3.2)
Fixed versions4.2.28, 5.2.11, 6.0.2 (2026-02-03); HeroDevs NES 3.2.27 (2026-04-29); Red Hat backport tracked in RHBZ 2436338
Preconditions narrowing exposureRequires django.contrib.gis + PostGIS + RasterField lookup + user-controllable *band index* + authenticated session — GIS raster is a niche subset of Django
Disclosure2026-02-03 by Django Security Team, credited via Django security policy channel
Scanner probes seen?band=, /api/raster/search/?band= — CrowdSec published detection scenarios and IP list within 8 days of disclosure
04 · The Call

noisgate verdict.

Final Verdict
UPGRADED to HIGH (7.2/10)

The single most decisive factor is active in-the-wild exploitation combined with a data-tier blast radius: successful SQLi against PostgreSQL/PostGIS routinely converts vendor-labeled 'partial' confidentiality into full-schema egress including session tokens and password hashes. Vendor's MEDIUM rating undervalues the impact ceiling of unauthenticated-equivalent SQLi on a production database once a low-priv session is obtained.

HIGH Vulnerability mechanics and affected version ranges
HIGH In-the-wild exploitation (CrowdSec telemetry)
MEDIUM Enterprise exposure rate — depends on GeoDjango+PostGIS+RasterField adoption

Why this verdict

  • Active exploitation: CrowdSec confirmed real attack traffic from 2026-02-26 — this is no longer theoretical.
  • Impact underscored: SQLi against PostGIS with the Django app's DB role rarely stays 'partial' — union-based reads escalate to session/credential theft, which is why EPSS is already ~9.4%.
  • Role multiplier: The affected component is a Django *application tier* speaking to a *production database* — the blast radius from a successful chain is fleet-scale within the tenant (all rows accessible to the app user), and Django is one of the most widely deployed Python web frameworks. Not identity/hypervisor tier, but data-tier compromise plausibly ends in mass PII/credential egress.
  • Friction limiting upgrade to CRITICAL: PR:L requires authenticated access, and the vulnerable code path is only reachable in apps using GeoDjango + PostGIS + RasterField with user-controlled band index — a small fraction of Django's installed base.
  • Deployment-role floor: Django apps hosting regulated/PII data on PostgreSQL are canonically 'production database' tier. Any org running GeoDjango + PostGIS for real (geospatial platforms, insurance/agri/logistics analytics) hits the HIGH floor by role.

Why not higher?

CRITICAL requires either unauthenticated exploitation, a canonical high-value-role component (hypervisor/IdP/PAM/DC), or KEV listing. This CVE requires PR:L, the vulnerable code path is a niche feature, and CISA has not yet added it. Also, well-configured deployments already coerce band index via IntegerField in serializers, which entirely neutralizes the payload.

Why not lower?

MEDIUM undersells two facts: exploitation is *actively happening in the wild*, and the impact vector is SQLi against a production database — that outcome rarely stays partial once an attacker enumerates the schema. Any framework tracking real-world signals (EPSS ~9.4%, CrowdSec traffic) pushes this past MEDIUM.

05 · Compensating Control

What to do — in priority order.

  1. Coerce band index to int at the view/serializer boundary — Wrap every raster lookup input with DRF IntegerField / Django forms.IntegerField or an explicit int() cast with a validated range. This kills the payload before it reaches the ORM. Deploy within 30 days per noisgate mitigation SLA — same day if you can identify the endpoints.
  2. Deploy a WAF rule blocking non-integer values on band and adjacent raster params — Signature: reject any band (or similarly-named) parameter that doesn't match ^-?\d{1,3}$. F5, Cloudflare, AWS WAF, and ModSecurity CRS can all express this. Buys you time until the Django upgrade lands.
  3. Rotate the Django app's PostgreSQL role to least privilege — The app user should have no pg_read_server_files, no COPY FROM PROGRAM, and only SELECT/INSERT/UPDATE on its own schema. Row-level security on tables holding PII/tokens caps the blast radius even if SQLi lands.
  4. Enable log_statement='mod' or full statement logging on PostgreSQL for the app role — Feeds a SIEM detection for UNION SELECT, information_schema.tables reads, and pg_user reads originating from the Django app user.
  5. Rotate session keys and force reauth if you detect probes — If CrowdSec/WAF logs show real band-parameter injection attempts, assume session-cookie exfil and rotate SECRET_KEY + invalidate all sessions.
What doesn't work
  • Django DEBUG=False — hides errors but does not stop the injection itself; blind SQLi still works.
  • Prepared statements at the psycopg2 layer — the ORM does the concatenation *before* handing SQL to the driver; the driver has no chance to parameterize.
  • CSRF tokens — the attacker is authenticated and can obtain a fresh CSRF token as part of the normal auth flow.
  • Rate limiting alone — targeted attackers pace their requests; and even one successful UNION exfil per minute drains a small schema in a workday.
06 · Verification

Crowdsourced verification payload.

Run on any host that has your Django project's requirements.txt / poetry.lock / installed venv. Invoke as python check_cve_2026_1207.py /path/to/venv/bin/python (pass the interpreter of the environment you want to test) or omit the arg to test the current interpreter. No elevated privileges required.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# noisgate — CVE-2026-1207 detector (Django GIS RasterField SQLi)
# Exits 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
import subprocess, sys, re

FIXED = {
    (4, 2): (4, 2, 28),
    (5, 2): (5, 2, 11),
    (6, 0): (6, 0, 2),
}

def get_django_version(py):
    try:
        out = subprocess.check_output(
            [py, '-c', 'import django,sys;sys.stdout.write(django.get_version())'],
            stderr=subprocess.STDOUT, timeout=10).decode().strip()
        return out
    except Exception as e:
        print(f'UNKNOWN: could not import django ({e})')
        sys.exit(2)

def parse(v):
    m = re.match(r'^(\d+)\.(\d+)\.(\d+)', v)
    if not m:
        return None
    return tuple(int(x) for x in m.groups())

def main():
    py = sys.argv[1] if len(sys.argv) > 1 else sys.executable
    ver = get_django_version(py)
    parsed = parse(ver)
    if not parsed:
        print(f'UNKNOWN: unparseable Django version {ver}')
        sys.exit(2)
    major, minor, patch = parsed
    # Unsupported branches (3.2, 4.1, 5.0) are vulnerable unless HeroDevs NES applied
    if (major, minor) in [(3, 2), (4, 1), (5, 0)]:
        print(f'VULNERABLE: Django {ver} is on an unsupported branch; upstream did not evaluate. See HeroDevs NES for 3.2.')
        sys.exit(1)
    fixed = FIXED.get((major, minor))
    if fixed is None:
        print(f'UNKNOWN: Django {ver} branch not in fix table')
        sys.exit(2)
    if parsed >= fixed:
        print(f'PATCHED: Django {ver} >= {".".join(map(str, fixed))}')
        sys.exit(0)
    print(f'VULNERABLE: Django {ver} < {".".join(map(str, fixed))} — CVE-2026-1207')
    sys.exit(1)

if __name__ == '__main__':
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Treat this as HIGH, not MEDIUM, because exploitation is live and the impact is data-tier. Monday morning: (1) inventory every Django deployment and pull django.__version__ — the verification script above does this in one line; (2) for any app using django.contrib.gis with PostGIS, ship input coercion on band-index parameters and a WAF signature *today* — the noisgate mitigation SLA is ≤ 30 days for HIGH, but with active exploitation the effective clock is *hours*; (3) schedule the actual Django upgrade to 4.2.28 / 5.2.11 / 6.0.2 (or HeroDevs NES 3.2.27 if you're stranded) — the noisgate remediation SLA is ≤ 180 days for HIGH, but frankly any Django shop can land a point release inside a normal 2-week sprint. If your CrowdSec/WAF logs show real band= injection attempts pre-patch, rotate SECRET_KEY, invalidate sessions, and audit django_session and any tables holding tokens or password hashes.

Sources

  1. GitHub Advisory GHSA-mwm9-4648-f68q
  2. HeroDevs — CVE-2026-1207 technical writeup
  3. CrowdSec — Active exploitation report
  4. Red Hat Bugzilla 2436338
  5. SentinelOne vulnerability database
  6. OffSeq Threat Radar entry
  7. CVEFeed detail
  8. Django security policy
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.