SQL Injection: tre buone pratiche di programmazione T-SQL per evitarlo

In questo articolo vedremo alcune buone pratiche di programmazione SQL per attenuare il rischio di SQL Injection. Faremo in particolare riferimento al database SQL Server e quindi al linguaggio T-SQL, studiando:

  • come eseguire codice dinamico tramite la stored procedure sp_executesql invece di exec;
  • l’utilizzo di QUOTENAME;
  • come gestire elenchi di valori da passare dinamicamente a una IN.

Questo articolo non è certamente esaustivo, per quanto molto dettagliato. Inoltre è importante utilizzare buone pratiche di sviluppo anche sull’eventuale linguaggio che usiamo per interfacciarci con il database (PHP, Java, Python, eccetera).

Gestire i parametri con sp_executesql

Partiamo guardando un modo di programmare con l’SQL dinamico che non tiene in considerazione i problemi dell’SQL Injection. Per semplicità valorizziamo direttamente la variabile @Nome con un valore d’esempio, che nella realtà potrebbe essere calcolato con una query o passato in input a una stored procedure.

DECLARE @Nome VARCHAR(50) = 'Nicola'; 
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT * 
                      FROM Clienti 
                      WHERE Nome = '''
                      + @Nome 
                      + '''';
SELECT @SqlString;
EXEC (@SqlString);

Ribadiamo che questo non è il modo corretto di eseguire questo tipo di task. In questo caso stiamo inserendo direttamente all’interno della query dinamica il valore della variabile @Nome. Se ad esempio @Nome fosse valorizzato con

'Nicola''; DROP TABLE Clienti; SELECT '''

allora andremo a eseguire la query

SELECT * FROM Clienti WHERE Nome = 'Nicola'; 
DROP TABLE Clienti; 
SELECT ''

con esiti poco piacevoli. Possiamo fare un importante passo in avanti riscrivendo il codice in questo modo:

DECLARE @Nome VARCHAR(50) = 'Nicola';
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT * 
                      FROM Clienti 
                      WHERE Nome = @par_nome';
EXECUTE sp_executesql @SqlString,
                      N'@par_nome VARCHAR(50)',
                      @par_nome= @Nome; 

In questo modo la stringa che ci viene passata in input sarà utilizzata solamente per eseguire il filtro sulla colonna Nome, ma non entrerà all’interno del codice della query.

Utilizzare QUOTENAME

Quotename è una funzione T-SQL che permette di racchiudere il contenuto di una stringa all’interno di parentesi quadre. Tuttavia è diversa dalla semplice concatenazione con i caratteri “[“ e “]” in quanto gestisce anche l’inserimento di caratteri di escape nel caso le parentesi quadre fossero già presenti nella stringa di input. Tuttavia possiamo darle in input una stringa lunga al più 128 caratteri, altrimenti ci restituirà un NULL.

Dopo questa premessa, vediamo anche qui un modo potenzialmente pericoloso di costruire una query dinamica:

DECLARE @Table VARCHAR(50) = 'Clienti';
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT * FROM ' 
                     + @Table 
                     + ';';
SELECT @SqlString;
EXECUTE sp_executesql @SqlString;

Anche in questo caso se Table fosse valorizzata con

Clienti; DROP TABLE Clienti; SELECT ''''

saremmo nei guai. Possiamo risolvere il problema aggiungendo all’inizio la riga

SELECT @Table = QUOTENAME(@Table);

In questo caso in presenza di una stringa malevola andremo semplicemente a interrogare una tabella che non esiste.

Sostituire IN con una JOIN

Supponiamo di avere una variabile che contenga una lista di numeri interi separati da una virgola. Vogliamo utilizzarla per costruire la clausola IN di una Query che effettua un filtro su una colonna di interi. Anche in questo caso il modo forse “più naturale” di programmare non è quello più corretto.

DECLARE @lista_in VARCHAR(50) = '1,2,3';
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT *
                      FROM Clienti
                      WHERE IdCliente IN ('
                      + @lista_in 
                      + ');';
SELECT @SqlString; 
EXEC sp_executesql @SqlString;

Potremmo infatti costruire abbastanza facilmente una stringa malevola. L’idea per risolvere il problema è quella di creare una subquery con l’elenco distinto di numeri presenti nella lista, per poi riscrivere la query utilizzando una INNER JOIN invece dell’IN. Dalla versione 2016 di SQL Server, possiamo raggiungere questo scopo abbastanza facilmente tramite la funzione STRING_SPLIT (qui la documentazione ufficiale della funzione https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver16). Scriveremo:

DECLARE @lista_in VARCHAR(50) = '1,2,3';
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT *
    FROM Clienti AS C
     INNER JOIN (SELECT DISTINCT 
                  CONVERT(INT,value) AS Id
	          FROM STRING_SPLIT(@par_lista_in,'','')
		 ) AS S
  ON C.IdCliente = S.Id';
EXEC sp_executesql @SqlString,
		   N'@par_lista_in VARCHAR(50)', 
		   @par_lista_in= @lista_in;

Alcune osservazioni sulla query;

  • la funzione STRING_SPLIT non garantisce l’ordinamento dei dati dopo lo split, tuttavia per i fini della nostra query ciò è del tutto irrilevante;
  • la DISTINCT permette di avere una query equivalente a quella con la IN anche nel caso fossero presenti dei duplicati all’interno dell’elenco di valori di lista_in;
  • sp_executesql ci permette ancora una volta di evitare di costruire il codice SQL utilizzando i valori della variabile.

Vi riporto infine il link a un paio di articoli che trattano l’argomento in modo molto dettagliato https://portswigger.net/web-security/sql-injection e https://www.sqlservercentral.com/articles/sql-injection si tratta comunque di un argomento talmente vasto che non si finirà mai di imparare e di aggiornarsi!

Continua a imparare

In questo articolo abbiamo visto tre metodi pratici per migliorare i nostri paradigmi di programmazione con il T-SQL dinamico e renderli resilienti ad attacchi di SQL Injection. In particolare un ruolo chiave è giocato dalla stored procedure di sistema sp_executesql che permette una gestione migliore dei parametri.

Scroll to Top
Torna su