mirror of
https://github.com/django/django.git
synced 2026-02-09 02:49:25 +08:00
[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 81aa529296 from main.
This commit is contained in:
@@ -51,6 +51,9 @@ 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 "
|
||||
@@ -62,6 +65,9 @@ class PostGISOperator(SpatialOperator):
|
||||
)
|
||||
|
||||
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 "
|
||||
|
||||
@@ -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>`.
|
||||
|
||||
@@ -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
|
||||
@@ -356,6 +360,47 @@ class RasterFieldTest(TransactionTestCase):
|
||||
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 = (
|
||||
|
||||
Reference in New Issue
Block a user