
Un análisis básico sobre CVE-2021-35942. Inyección SQL en Django.
Django es un framework de aplicaciones web de código abierto, escrito en Python y construido según el modelo MVC (Model - View - Controller). Originalmente fue creado para gestionar sitios web de contenido de noticias propiedad de la corporación editorial Lawrence, como software CMS (Content Management System).
Las versiones de Django 3.1.x -> 3.1.13 y 3.2.x -> 3.2.5 presentan una vulnerabilidad de inyección SQL.
La causa de esta vulnerabilidad es que la función de filtrado de datos de entrada controlados por el usuario en QuerySet.order_by() no es suficiente para prevenir ataques de inyección SQL. Esta vulnerabilidad puede ser explotada para permitir que un atacante realice acciones no autorizadas que conduzcan a la filtración de datos sensibles.
| CVE - ID | CVE-2021-35042 |
|---|---|
| Severidad | 9.8 - CRÍTICA |
| CWE - ID | CWE-89: Neutralización incorrecta de elementos especiales utilizados en un comando SQL ('Inyección SQL') |
| Fecha de publicación de la vulnerabilidad | 1/7/2021 |
| Software afectado | 3.1.x < 3.1.13, 3.2.x < 3.2.5 |
| Requiere autenticación | No requerida |
En Django, la creación de tablas y la definición de campos en la base de datos se realiza declarando una clase de modelo en el archivo models.py. En este ejemplo, declaramos una tabla llamada Wolf y un campo llamado name.


El framework ORM integrado en Django se utiliza para operar con la base de datos, y el resultado de la consulta es un conjunto, este conjunto es un QuerySet.
order_by(fields)
Por defecto, order_by() devuelve un QuerySet ordenado según un orden especificado en la opción ordering dentro del Meta del Modelo. Podemos sobrescribir la condición order_by en cada consulta utilizando el método order_by().
Ejemplo
wolves = Wolf.objects.order_by('-name', 'id')
El resultado de la consulta anterior se ordenará de forma descendente por el campo name, y luego de forma ascendente por id. El signo negativo delante del nombre del campo name indica que el resultado se ordena de forma descendente.
El siguiente ejemplo ordenará el resultado devuelto según el campo recibido del usuario; si no se pasa ningún valor, se ordenará por el campo
id.
Resultado

En las versiones 3.1 y 3.2, Django permite combinar el método de consulta con el nombre de la tabla en la consulta order_by. Esta es también la causa principal de esta vulnerabilidad.
Pasar un nombre de tabla nos da el mismo resultado que pasar un nombre de campo normal
cve202135042_wolf es el nombre de la tabla
Primero, la aplicación llama directamente a la función order_by(); el código que procesa la función order_by() está definido en:
django/db/models/query.py

La función order_by() realiza dos tareas
- Elimina todos los métodos actuales que están siendo llamados por order_by() y elimina el parámetro predeterminado que se pasa cuando order_by recibe un valor diferente.
- Pasa el parámetro a order_by. La función
add_ordering()realiza esta tarea.
def add_ordering(self, *ordering):
"""
Add items from the 'ordering' sequence to the query's "order by"
clause. These items are either field names (not column names) --
possibly with a direction prefix ('-' or '?') -- or OrderBy
expressions.
If 'ordering' is empty, clear all ordering from the query.
"""
errors = []
for item in ordering:
if isinstance(item, str):
if '.' in item:
warnings.warn(
'Passing column raw column aliases to order_by() is '
'deprecated. Wrap %r in a RawSQL expression before '
'passing it to order_by().' % item,
category=RemovedInDjango40Warning,
stacklevel=3,
)
continue
if item == '?':
continue
if item.startswith('-'):
item = item[1:]
if item in self.annotations:
continue
if self.extra and item in self.extra:
continue
# names_to_path() validates the lookup. A descriptive
# FieldError will be raise if it's not.
self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
elif not hasattr(item, 'resolve_expression'):
errors.append(item)
if getattr(item, 'contains_aggregate', False):
raise FieldError(
'Using an aggregate in order_by() without also including '
'it in annotate() is not allowed: %s' % item
)
if errors:
raise FieldError('Invalid order_by arguments: %s' % errors)
if ordering:
self.order_by += ordering
else:
self.default_ordering = False
El parámetro pasado a add_ordering() es un array.
Por ejemplo, cuando el parámetro se pasa de la siguiente manera:
wolves = Wolf.objects.order_by( 'name' , 'id' )Entonces, la aplicación convertirá esto en la siguiente consulta en la base de datos:
SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY "cve202135042_wolf"."name" ASC, "cve202135042_wolf"."id" ASC
Cuando se pasa, la función add_ordering verificará cada elemento del array; si es un string, se comprobarán los siguientes 5 casos:
if '.' in item:Comprueba si es una consulta con un nombre de columna y si esa columna tiene un nombre de tabla especificado en la sentencia SQL. Si es así, se emite una advertencia y se ejecutacontinue.if item == '?':Si el valor del elemento es el signo '?', el resultado de salida se ordenará aleatoriamente,continue.if item.startswith('-'):Si el elemento comienza con el carácter '-', el resultado de la consulta se ordenará DESC (descendente).if item in self.annotations:Comprueba si contiene un comentario; si es así,continue.if self.extra and item in self.extra:Determina si hay adiciones y, si es así,continue.