
Vulnerabilità di SQL injection in Django
Django è un Web Application Framework open source, scritto in Python, costruito secondo il modello MVC (Model - View - Controller). Inizialmente è stato creato per gestire i siti web di contenuti di notizie di proprietà del gruppo editoriale Lawrence, software CMS (Content Management System).
Le versioni di Django 3.1.x -> 3.1.13 e 3.2.x -> 3.2.5 presentano una vulnerabilità di SQL injection.
La causa di questa vulnerabilità è che la funzione di filtro dei dati di input controllati dall'utente in QuerySet.order_by() non è sufficiente a prevenire gli attacchi di SQL injection. Questa vulnerabilità può essere sfruttata per consentire a un attaccante di compiere azioni non autorizzate, portando alla divulgazione di dati sensibili.
| CVE - ID | CVE-2021-35042 |
|---|---|
| Severità | 9.8 - CRITICA |
| CWE - ID | CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') |
| Data di pubblicazione della vulnerabilità | 1/7/2021 |
| Software interessato | 3.1.x < 3.1.13, 3.2.x < 3.2.5 |
| Autenticazione richiesta | Non richiesta |
In Django, la creazione delle tabelle e la definizione dei campi nel database viene eseguita dichiarando una classe model nel file models.py. In questo esempio, dichiariamo una tabella chiamata Wolf e un campo chiamato name.


Il framework ORM integrato in Django viene utilizzato per interagire con il database e il risultato della query è un insieme, chiamato QuerySet.
order_by(fields)
Per impostazione predefinita, order_by() restituisce un QuerySet ordinato in base all'ordine specificato nell'opzione ordering nel Meta del Model. Possiamo sovrascrivere la condizione order_by in ogni query utilizzando il metodo order_by().
Esempio
wolves = Wolf.objects.order_by('-name', 'id')
Il risultato della query precedente verrà ordinato in modo decrescente per il campo name, poi in modo crescente per id. Il segno meno davanti al nome del campo name indica che il risultato è ordinato in modo decrescente.
L'esempio seguente ordina i risultati in base al campo ricevuto dall'utente; se non viene passato alcun valore, ordina per il campo
id.
Risultato

Nelle versioni 3.1 e 3.2, Django consente di combinare il metodo di query con il nome della tabella nella query order_by. Questa è anche la causa principale di questa vulnerabilità.
Passare un nome di tabella ci dà lo stesso risultato del passaggio del nome di un campo normalmente
cve202135042_wolf è il nome della tabella
Innanzitutto l'applicazione chiama direttamente la funzione order_by(); il codice che gestisce la funzione order _by() è definito in:
django/db/models/query.py

La funzione order_by() esegue 2 operazioni
- Rimuove tutti i metodi attualmente chiamati da order_by() e rimuove il parametro predefinito passato quando order_by riceve un valore diverso.
- Passa i parametri a order_by. È la funzione
add_ordering()a eseguire questa operazione.
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
I parametri passati a add_ordering() sono un array.
Esempio: quando vengono passati i seguenti parametri:
wolves = Wolf.objects.order_by( 'name' , 'id' )In tal caso, l'applicazione li convertirà nella seguente query nel database:
SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY "cve202135042_wolf"."name" ASC, "cve202135042_wolf"."id" ASC
Quando vengono passati, la funzione add_ordering controlla ogni elemento dell'array; se si tratta di una string, vengono verificati i seguenti 5 casi:
if '.' in item:Controlla se si tratta di una query con un nome di colonna e se quella colonna ha un nome di tabella specificato nell'istruzione SQL. Se presente, viene emesso un avviso econtinue.if item == '?':Se il valore dell'elemento è il carattere '?', il risultato dell'output viene ordinato casualmente,continue.if item.startswith('-'):Se l'item inizia con il carattere '-', il risultato della query viene ordinato in modo DESC (decrescente).if item in self.annotations:Controlla se contiene un commento; se sì,continue.if self.extra and item in self.extra:Determina se sono presenti extra e, se sì,continue.
Dopo i 5 controlli, il parametro viene passato alla funzione self.names_to_path(item.split(LOOKUP_SEP), self.model._meta) per verificare ulteriormente se si tratta di un nome di colonna valido; se valido, viene aggiunto a self.ordering della classe Query per l'elaborazione successiva.