High severityOSV Advisory· Published Feb 3, 2026· Updated Feb 3, 2026
Potential SQL injection via raster lookups on PostGIS
CVE-2026-1207
Description
An issue was discovered in 6.0 before 6.0.2, 5.2 before 5.2.11, and 4.2 before 4.2.28. Raster lookups on `RasterField` (only implemented on PostGIS) allows remote attackers to inject SQL via the band index parameter. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Tarek Nakkouch for reporting this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 6.0a1, < 6.0.2 | 6.0.2 |
DjangoPyPI | >= 5.2a1, < 5.2.11 | 5.2.11 |
DjangoPyPI | >= 4.2a1, < 4.2.28 | 4.2.28 |
Affected products
1- Range: 4.2, 4.2.1, 4.2.10, …
Patches
181aa5292967cFixed CVE-2026-1207 -- Prevented SQL injections in RasterField lookups via band index.
5 files changed · +88 −1
django/contrib/gis/db/backends/postgis/operations.py+6 −0 modified@@ -51,6 +51,9 @@ def check_raster(self, lookup, template_params): # Look for band indices and inject them if provided. if lookup.band_lhs is not None and lhs_is_raster: + if not isinstance(lookup.band_lhs, int): + name = lookup.band_lhs.__class__.__name__ + raise TypeError(f"Band index must be an integer, but got {name!r}.") if not self.func: raise ValueError( "Band indices are not allowed for this operator, it works on bbox " @@ -62,6 +65,9 @@ def check_raster(self, lookup, template_params): ) if lookup.band_rhs is not None and rhs_is_raster: + if not isinstance(lookup.band_rhs, int): + name = lookup.band_rhs.__class__.__name__ + raise TypeError(f"Band index must be an integer, but got {name!r}.") if not self.func: raise ValueError( "Band indices are not allowed for this operator, it works on bbox "
docs/releases/4.2.28.txt+12 −0 modified@@ -29,3 +29,15 @@ produced super-linear computation resulting in service degradation or outage. This issue has severity "moderate" according to the :ref:`Django security policy <security-disclosure>`. + +CVE-2026-1207: Potential SQL injection via raster lookups on PostGIS +==================================================================== + +:ref:`Raster lookups <spatial-lookup-raster>` on GIS fields (only implemented +on PostGIS) were subject to SQL injection if untrusted data was used as a band +index. + +As a reminder, all untrusted user input should be validated before use. + +This issue has severity "high" according to the :ref:`Django security policy +<security-disclosure>`.
docs/releases/5.2.11.txt+12 −0 modified@@ -29,3 +29,15 @@ produced super-linear computation resulting in service degradation or outage. This issue has severity "moderate" according to the :ref:`Django security policy <security-disclosure>`. + +CVE-2026-1207: Potential SQL injection via raster lookups on PostGIS +==================================================================== + +:ref:`Raster lookups <spatial-lookup-raster>` on GIS fields (only implemented +on PostGIS) were subject to SQL injection if untrusted data was used as a band +index. + +As a reminder, all untrusted user input should be validated before use. + +This issue has severity "high" according to the :ref:`Django security policy +<security-disclosure>`.
docs/releases/6.0.2.txt+12 −0 modified@@ -30,6 +30,18 @@ produced super-linear computation resulting in service degradation or outage. This issue has severity "moderate" according to the :ref:`Django security policy <security-disclosure>`. +CVE-2026-1207: Potential SQL injection via raster lookups on PostGIS +==================================================================== + +:ref:`Raster lookups <spatial-lookup-raster>` on GIS fields (only implemented +on PostGIS) were subject to SQL injection if untrusted data was used as a band +index. + +As a reminder, all untrusted user input should be validated before use. + +This issue has severity "high" according to the :ref:`Django security policy +<security-disclosure>`. + Bugfixes ========
tests/gis_tests/rasterapp/test_rasterfield.py+46 −1 modified@@ -2,7 +2,11 @@ from django.contrib.gis.db.models.fields import BaseSpatialField from django.contrib.gis.db.models.functions import Distance -from django.contrib.gis.db.models.lookups import DistanceLookupBase, GISLookup +from django.contrib.gis.db.models.lookups import ( + DistanceLookupBase, + GISLookup, + RasterBandTransform, +) from django.contrib.gis.gdal import GDALRaster from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.measure import D @@ -356,6 +360,47 @@ def test_lookup_input_band_not_allowed(self): with self.assertRaisesMessage(ValueError, msg): qs.count() + def test_lookup_invalid_band_rhs(self): + rast = GDALRaster(json.loads(JSON_RASTER)) + qs = RasterModel.objects.filter(rast__contains=(rast, "evil")) + msg = "Band index must be an integer, but got 'str'." + with self.assertRaisesMessage(TypeError, msg): + qs.count() + + def test_lookup_invalid_band_lhs(self): + """ + Typical left-hand side usage is protected against non-integers, but for + defense-in-depth purposes, construct custom lookups that evade the + `int()` and `+ 1` checks in the lookups shipped by django.contrib.gis. + """ + + # Evade the int() call in RasterField.get_transform(). + class MyRasterBandTransform(RasterBandTransform): + band_index = "evil" + + def process_band_indices(self, *args, **kwargs): + self.band_lhs = self.lhs.band_index + self.band_rhs, *self.rhs_params = self.rhs_params + + # Evade the `+ 1` call in BaseSpatialField.process_band_indices(). + ContainsLookup = RasterModel._meta.get_field("rast").get_lookup("contains") + + class MyContainsLookup(ContainsLookup): + def process_band_indices(self, *args, **kwargs): + self.band_lhs = self.lhs.band_index + self.band_rhs, *self.rhs_params = self.rhs_params + + RasterField = RasterModel._meta.get_field("rast") + RasterField.register_lookup(MyContainsLookup, "contains") + self.addCleanup(RasterField.register_lookup, ContainsLookup, "contains") + + qs = RasterModel.objects.annotate( + transformed=MyRasterBandTransform("rast") + ).filter(transformed__contains=(F("transformed"), 1)) + msg = "Band index must be an integer, but got 'str'." + with self.assertRaisesMessage(TypeError, msg): + list(qs) + def test_isvalid_lookup_with_raster_error(self): qs = RasterModel.objects.filter(rast__isvalid=True) msg = (
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
8- docs.djangoproject.com/en/dev/releases/security/mitrevendor-advisory
- github.com/advisories/GHSA-mwm9-4648-f68qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-1207ghsaADVISORY
- www.djangoproject.com/weblog/2026/feb/03/security-releases/mitrevendor-advisory
- docs.djangoproject.com/en/dev/releases/securityghsaWEB
- github.com/django/django/commit/81aa5292967cd09319c45fe2c1a525ce7b6684d8ghsaWEB
- groups.google.com/g/django-announceghsamailing-listWEB
- www.djangoproject.com/weblog/2026/feb/03/security-releasesghsaWEB
News mentions
0No linked articles in our index yet.