Questo codice funziona correttamente perché è:
- Parametrizzato e
- Non eseguire alcun SQL dinamico
Affinché SQL Injection funzioni, è necessario creare una stringa di query (cosa che non si sta eseguendo) e non tradurre singoli apostrofi ( '
) in apostrofi di escape ( ''
) (questi vengono salvati tramite i parametri di input).
Nel tuo tentativo di passare un valore "compromesso", la 'Male; DROP TABLE tblActor'
stringa è proprio quella, una stringa semplice.
Ora, se stessi facendo qualcosa sulla falsariga di:
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'SELECT fields FROM table WHERE field23 = '
+ @InputParam;
EXEC(@SQL);
poi che sarebbe suscettibile di SQL Injection, perché che query non è nell'attuale contesto pre-analizzata; quella query è solo un'altra stringa al momento. Quindi il valore di @InputParam
potrebbe essere '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
e ciò potrebbe presentare un problema perché la query verrebbe rappresentata ed eseguita come:
SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
Questo è uno (di diversi) motivi principali per utilizzare Stored Procedures: intrinsecamente più sicuro (beh, purché tu non aggiri quella sicurezza creando query come ho mostrato sopra senza convalidare i valori di tutti i parametri utilizzati). Tuttavia, se è necessario creare Dynamic SQL, il modo preferito è parametrizzare anche utilizzando sp_executesql
:
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp';
EXEC sp_executesql
@SQL,
N'SomeDate_tmp DATETIME',
@SomeDate_tmp = @InputParam;
Utilizzando questo approccio, qualcuno che tenta di passare '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
per un DATETIME
parametro di input riceverà un errore durante l'esecuzione della Stored Procedure. O anche se la Procedura memorizzata accettasse @InputParameter
come NVARCHAR(100)
, dovrebbe DATETIME
passare a un per passare a quella sp_executesql
chiamata. E anche se il parametro in Dynamic SQL è un tipo di stringa, arrivando alla Stored Procedure in primo luogo ogni singolo apostrofo verrebbe automaticamente evaso in un doppio apostrofo.
Esiste un tipo di attacco meno noto in cui l'attaccante tenta di riempire il campo di input con apostrofi in modo tale che una stringa all'interno della Stored Procedure che verrebbe utilizzata per costruire l'SQL dinamico ma che è dichiarata troppo piccola non può adattarsi a tutto ed elimina l'apostrofo finale e in qualche modo finisce con il numero corretto di apostrofi in modo da non essere più "evaso" all'interno della stringa. Questo si chiama SQL Truncation ed è stato discusso in un articolo della rivista MSDN intitolato "Nuovi attacchi di troncamento SQL e come evitarli", di Bala Neerumalla, ma l'articolo non è più online. Il problema contenente questo articolo , l'edizione di novembre 2006 di MSDN Magazine , è disponibile solo come file della Guida di Windows (in .chmformato). Se lo scarichi, potrebbe non aprirsi a causa delle impostazioni di sicurezza predefinite. In tal caso, fare clic con il pulsante destro del mouse sul file MSDNMagazineNovember2006en-us.chm e selezionare "Proprietà". In una di quelle schede ci sarà un'opzione per "Fidati di questo tipo di file" (o qualcosa del genere) che deve essere selezionata / abilitata. Fare clic sul pulsante "OK", quindi provare ad aprire nuovamente il file .chm .
Un'altra variante dell'attacco di troncamento è, supponendo che una variabile locale sia utilizzata per memorizzare il valore "sicuro" fornito dall'utente in quanto aveva doppie virgolette raddoppiate in modo da sfuggire, per riempire quella variabile locale e posizionare la virgoletta singola alla fine. L'idea qui è che se la variabile locale non è dimensionata correttamente, non ci sarà abbastanza spazio alla fine per la seconda virgoletta singola, lasciare la variabile che termina con una singola virgoletta singola che poi si combina con la virgoletta singola che termina il valore letterale in SQL dinamico, trasformando quella virgoletta singola finale in una virgoletta singola con escape incorporata, e la stringa letterale in SQL dinamico termina quindi con la virgoletta singola successiva che intendeva iniziare la stringa letterale successiva. Per esempio:
-- Parameters:
DECLARE @UserID INT = 37,
@NewPassword NVARCHAR(15) = N'Any Value ....''',
@OldPassword NVARCHAR(15) = N';Injected SQL--';
-- Stored Proc:
DECLARE @SQL NVARCHAR(MAX),
@NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''),
@OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N'''''');
SELECT @NewPassword AS [@NewPassword],
REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output],
@NewPassword_fixed AS [@NewPassword_fixed];
/*
@NewPassword REPLACE output @NewPassword_fixed
Any Value ....' Any Value ....'' Any Value ....'
*/
SELECT @OldPassword AS [@OldPassword],
REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output],
@OldPassword_fixed AS [@OldPassword_fixed];
/*
@OldPassword REPLACE output @OldPassword_fixed
;Injected SQL-- ;Injected SQL-- ;Injected SQL--
*/
SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
+ @NewPassword_fixed + N''' WHERE [TableNameID] = '
+ CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
+ @OldPassword_fixed + N''';';
SELECT @SQL AS [Injected];
Qui, l'SQL dinamico da eseguire è ora:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Lo stesso SQL dinamico, in un formato più leggibile, è:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';
Injected SQL--';
Risolvere questo è facile. Basta eseguire una delle seguenti operazioni:
- NON UTILIZZARE SQL DINAMICO A MENO ASSOLUTAMENTE NECESSARIO! (Lo sto elencando prima perché dovrebbe davvero essere la prima cosa da considerare).
- Ridimensiona correttamente la variabile locale (ovvero dovrebbe essere doppia rispetto al parametro di input, nel caso in cui tutti i caratteri passati siano tra virgolette singole.
Non utilizzare una variabile locale per memorizzare il valore "fisso"; basta inserire REPLACE()
direttamente nella creazione di Dynamic SQL:
SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
+ REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = '
+ CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
+ REPLACE(@OldPassword, N'''', N'''''') + N''';';
SELECT @SQL AS [No SQL Injection here];
Il Dynamic SQL non è più compromesso:
UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Note sull'esempio di Trunction sopra:
- Sì, questo è un esempio molto ingegnoso. Non c'è molto che si possa fare in soli 15 caratteri da iniettare. Certo, forse un
DELETE tableName
per essere distruttivo, ma meno probabilità di aggiungere un utente back-door o modificare una password di amministratore.
- Questo tipo di attacco probabilmente richiede la conoscenza del codice, dei nomi delle tabelle, ecc. Meno probabile che venga fatto da uno sconosciuto / script-kiddie casuale, ma ho lavorato in un posto che è stato attaccato da un ex dipendente piuttosto turbato che sapeva di una vulnerabilità in una particolare pagina Web di cui nessun altro era a conoscenza. Significato, a volte gli aggressori hanno una conoscenza intima del sistema.
- Certo, è probabile che venga ripristinata la reimpostazione della password di tutti , il che potrebbe avvisare l'azienda che si sta verificando un attacco, ma potrebbe comunque fornire abbastanza tempo per iniettare un utente back-door o forse ottenere alcune informazioni secondarie da utilizzare / sfruttare in seguito.
- Anche se questo scenario è per lo più accademico (cioè non è probabile che accada nel mondo reale), non è ancora impossibile.
Per informazioni più dettagliate relative a SQL Injection (relative a vari RDBMS e scenari), consultare quanto segue da Open Web Application Security Project (OWASP):
Test per SQL Injection
Stack Overflow correlato risposta su SQL Injection e SQL Truncation:
quanto è sicuro T-SQL dopo aver sostituito il carattere 'escape?
EXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'