File CVE-2026-1207.patch of Package python-Django.19338
From 891f01ea595df4bef7415454d2cf57b265bbd573 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Mon, 19 Jan 2026 15:42:33 -0500
Subject: [PATCH 3/7] [4.2.x] Fixed CVE-2026-1207 -- Prevented SQL injections
in RasterField lookups via band index.
Thanks Tarek Nakkouch for the report, and Simon Charette for the initial
triage and review.
Backport of e8fb40508f4ca8c836caf985afc60d286cafec48 from main.
---
.../gis/db/backends/postgis/operations.py | 6 +++
docs/releases/4.2.28.txt | 12 +++++
tests/gis_tests/rasterapp/test_rasterfield.py | 47 ++++++++++++++++++-
3 files changed, 64 insertions(+), 1 deletion(-)
Index: Django-2.2.28/django/contrib/gis/db/backends/postgis/operations.py
===================================================================
--- Django-2.2.28.orig/django/contrib/gis/db/backends/postgis/operations.py
+++ Django-2.2.28/django/contrib/gis/db/backends/postgis/operations.py
@@ -54,11 +54,17 @@ class PostGISOperator(SpatialOperator):
# 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 only.')
template_params['lhs'] = '%s, %s' % (template_params['lhs'], lookup.band_lhs)
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 only.')
template_params['rhs'] = '%s, %s' % (template_params['rhs'], lookup.band_rhs)
Index: Django-2.2.28/tests/gis_tests/rasterapp/test_rasterfield.py
===================================================================
--- Django-2.2.28.orig/tests/gis_tests/rasterapp/test_rasterfield.py
+++ Django-2.2.28/tests/gis_tests/rasterapp/test_rasterfield.py
@@ -2,7 +2,11 @@ import json
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
@@ -282,6 +286,47 @@ class RasterFieldTest(TransactionTestCas
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 = 'IsValid function requires a GeometryField in position 1, got RasterField.'