Security
Apr 30, 2026

CVE-2026-1207: SQL Injection in Django Raster Lookups (PostGIS)

How a missed parameterization in PostGIS raster band index lookups exposes every Django version, including the unevaluated EOL ones

Give me the TL;DR
CVE-2026-1207: SQL Injection in Django Raster Lookups (PostGIS)
For Qualys admins, NES for .NET directly resolves the EOL/Obsolete Software:   Microsoft .NET Version 6 Detected vulnerability, ensuring your systems remain secure and compliant. Fill out the form to get pricing details and learn more.

CVE-2026-1207 is a SQL injection vulnerability in Django's GIS layer, in the raster lookup path on RasterField (PostGIS-only). Untrusted data flowing into the band index parameter is concatenated into the generated SQL rather than parameterized. Django classifies the issue as "high" severity in its security policy and shipped fixes on February 3, 2026 in 6.0.2, 5.2.11, and 4.2.28.

The official advisory only evaluated currently-supported branches. Every earlier Django series, explicitly including 3.2.x, 4.1.x, and 5.0.x, was not evaluated and may also be affected. With Django 4.2 LTS reaching end of life on April 7, 2026, the population of Django installs running without an upstream fix path for this CVE just grew significantly. HeroDevs has investigated the unevaluated branches, confirmed Django 3.2 is affected, and shipped a patched drop-in replacement in NES for Django 3.2.27.

What is CVE-2026-1207?

CVE-2026-1207 is a SQL injection (CWE-89) in Django's geographic information system (GIS) layer, specifically in raster lookups on the RasterField model field, which is only implemented for PostGIS. Raster lookups let applications query and filter against raster (gridded) image data stored in PostGIS, with the band index parameter selecting which raster band the query operates against.

The vulnerable behavior: when an application passes a raster lookup with a band index value derived from untrusted input, Django generates SQL that inlines the band index into the query string rather than passing it as a bound parameter. An attacker who controls the band index value can break out of the intended position in the query and inject arbitrary SQL.

This is a different class of bug from a typical Django ORM SQL injection. Django's standard ORM is parameterized end to end and has earned the framework a strong reputation for SQLi resistance. The GIS raster lookup path is a narrower surface, but it bypasses that guarantee for any application that exposes raster band selection to user input.

The reporter, Tarek Nakkouch, disclosed the issue to Django following coordinated disclosure norms.

How Django scored this CVE: CNA v3.1 vs Django security policy

The two scores most readers will see do not match, and the difference matters.

The CVSS v3.1 vector is the score of record in NVD. The two parameters worth highlighting:

  • Privileges Required: Low (PR:L): the attacker must be an authenticated user of the application, not an anonymous internet user. This is not an unauthenticated remote SQLi. Any guidance treating it as such is wrong.
  • Confidentiality and Integrity: Low (C:L/I:L): the CNA scored the worst case as partial read or partial write to the database, not full takeover. This is the largest single reason the score lands at 5.4 rather than higher.

The Django security policy independently classifies the issue as "high" because the underlying defect is a SQL injection in a framework method that developers reasonably expect to be parameterized. The two views are not contradictory: the CVSS score reflects the strict per-vector exploit characteristics, and the project's qualitative classification reflects the breadth of concern given the framework's overall security posture.

For risk decisions, treat the CVSS vector as authoritative on what an attack looks like, and treat the Django classification as authoritative on patch urgency.

Exploit conditions

The attack requires all of the following:

  • The application uses Django GIS (django.contrib.gis) on a PostgreSQL database with the PostGIS extension enabled.
  • The application defines one or more RasterField model fields and exposes raster lookup queries to user-influenced input.
  • The application allows an authenticated user to control the band index value passed into a raster lookup, either directly via a query parameter or indirectly through a form, API, or stored value that flows into the lookup.
  • The application does not validate the band index against an integer-only allowlist before passing it to the ORM.

The PostGIS-only constraint is the strongest filter: most Django applications run on standard PostgreSQL, MySQL, or SQLite, and are not affected. Among PostGIS users, applications that use raster data are a smaller subset still, and applications that expose raster band selection to user input are smaller again. But "smaller subset" is not "no one": GIS-heavy applications in mapping, agriculture, climate, defense, insurance, and logistics are real, and many run on long-lived Django LTS branches where migration is non-trivial.

Active exploitation in the wild

CrowdSec was the first to confirm exploitation of CVE-2026-1207 in the wild. Per the CrowdSec VulnTracking Report published March 23, 2026, the timeline of first observed activity is:

  • February 3, 2026: Django publishes the advisory and ships fixes.
  • February 18, 2026: CrowdSec releases a detection rule for the network.
  • February 26, 2026: First attacks targeting CVE-2026-1207 observed by the CrowdSec network.

The exploitation pattern reported by CrowdSec is a steady week-over-week stream rather than a volumetric spike, with attacks focused on identifying vulnerable Django and PostGIS configurations. CrowdSec characterizes the activity as sophisticated targeting, not broad opportunistic spraying. Observed payloads probe endpoints with parameters like /?band= and /api/raster/search/?band=, consistent with the public advisory's description of the band index as the injection point.

The CVE has not been added to the CISA Known Exploited Vulnerabilities catalog at the time of writing. CrowdSec expects that to change. For Django operators, the practical implication is that the window between "patch is available" and "you are being scanned" has already closed: applications matching the exploit conditions in the previous section should treat patching as time-sensitive, not routine.

A note on the CrowdSec writeup: the report describes the impact as "attackers could potentially bypass authentication, access sensitive user data, or modify database contents." The "bypass authentication" framing should be read carefully against the CVSS vector. The vulnerability is PR:L, meaning an authenticated user is required to reach the vulnerable code path. What an exploit can bypass is authorization at the database layer, not authentication at the application layer. This distinction matters for risk modeling: the threat profile is "abusive insider or compromised low-privilege account" rather than "anonymous internet attacker."

Why this affects Django 3.2

The Django security advisory is unusually explicit about the limits of its evaluation. The published text reads:

"Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected."

This is a familiar pattern for organizations on EOL Django: the upstream project has neither the resources nor the obligation to investigate, score, or patch series that have passed their support window. The CVE is published, the patch is shipped to supported branches, and everything older is left as an open question. Readers running 3.2 in production are responsible for either confirming non-applicability themselves or finding a vendor that will.

HeroDevs investigated the raster lookup path on Django 3.2 and confirmed the same defective code path is present. The fix from upstream is not a clean cherry-pick onto 3.2 because the surrounding GIS module has shifted across releases, but the underlying parameterization change is well-defined. NES for Django 3.2.27 (released April 29, 2026) ships the patch alongside fixes for four other CVEs covered in the same release: CVE-2026-4292, CVE-2025-13473, CVE-2026-4277, and CVE-2026-25674.

If your organization is running Django 3.2 by choice, by acquisition, or by the gravitational pull of an integrated platform that has not yet been migrated, this CVE is not a hypothetical. See Trapped on Django 3.2: How Enterprises Can Balance Compliance and Migration Reality for the broader context on the EOL gap, or NES for Django 3.2 for the support coverage details.

Affected versions

EOL dates sourced from the Django release support timeline.

The 4.2 row deserves a separate note. The upstream patch in 4.2.30 was the last security release for the 4.2 LTS branch; 4.2 reached end of life on April 7, 2026. Organizations that adopted 4.2 LTS specifically to defer a major version migration are now in the same position as 3.2 organizations were two years ago: a supported security release exists for this CVE, but no upstream support exists for the next one.

Impact

Successful exploitation gives an authenticated attacker the ability to inject SQL into the query generated by a raster lookup. Concrete impacts depend on the database role the Django application connects as, but typically include:

  • Data exfiltration of rows from any table the application's database user can read, including user accounts, session data, and adjacent business records, via boolean or time-based blind SQLi techniques. The CVSS scope of C:L reflects partial information disclosure, not the absence of disclosure.
  • Data tampering of rows the application's database user can write, including modification of records the attacker would not normally be authorized to touch through application logic. I:L in the vector reflects partial integrity loss.
  • Pivot to other vulnerabilities: a successful SQLi probe is often the first step in a longer chain. Information disclosed via this CVE (schema details, internal IDs, hashed credentials) can fuel later attacks against authentication, authorization, or session handling.

Availability impact is rated A:N (none) by the CNA, which is consistent with the lookup path: this is not a query that takes down the database. The risk profile is data theft and tampering, not denial of service.

Mitigation guidance

The defense-in-depth row matters even on patched versions: input validation is cheap, audit-friendly, and a reasonable habit independent of any specific CVE. If your team can find every RasterField lookup call site as part of this remediation, the same audit will catch unrelated exposure on the next GIS-layer CVE.

Taking action

CVE-2026-1207 is a textbook example of why "supported version" and "patched version" are not the same thing. Three currently-supported Django branches received fixes on February 3, 2026. Three EOL branches (and now four, including 4.2 as of April 7) did not receive any evaluation at all. The published advisory is explicit that the older branches "may also be affected," which is the closest thing to a vendor-issued "you're on your own" notice that an open source project produces.

For organizations in regulated sectors where the migration timeline collides with audit deadlines, this is a familiar shape. How Sanlam Private Wealth turned AngularJS EOL risk into strategic advantage covers the same pattern in a different framework: financial-services compliance posture, sensitive client data, and a migration that needs to happen on a calendar set by the business rather than the framework's release schedule. The Sanlam playbook generalizes to Django 3.2 holdouts in financial services, healthcare, and any other regulated environment.

If your organization runs Django 3.2 in production, NES for Django is the supported path for staying patched against this CVE and the four others shipped in the same April 29, 2026 release. The full per-version changelog, including all CVEs addressed in 3.2.27, lives in the NES for Django 3.2 release notes.

Table of Contents
Author
Greg Allen
Chief Technology Officer
Open Source Insights Delivered Monthly