6

C'è qualcos'altro che il codice deve fare per disinfettare gli identificatori (tabella, vista, colonna) tranne per racchiuderli tra virgolette doppie e virgolette doppie segni presenti nel nome dell'identificatore? I riferimenti sarebbero apprezzati.Correggere l'escape degli identificatori delimitati in SQL Server senza utilizzare QUOTENAME

Ho ereditato un codice base con un sistema ORM (Object-Relational Mapping) personalizzato. SQL non può essere scritto nell'applicazione, ma l'ORM deve ancora generare l'SQL da inviare a SQL Server. Tutti gli identificatori sono indicati con virgolette doppie.

string QuoteName(string identifier) 
{ 
    return "\"" + identifier.Replace("\"", "\"\"") + "\""; 
} 

Se fossi la costruzione di questo SQL dinamico in SQL, vorrei utilizzare il built-in funzione di SQL Server QUOTENAME:

declare @identifier nvarchar(128); 
set @identifier = N'Client"; DROP TABLE [dbo].Client; --'; 

declare @delimitedIdentifier nvarchar(258); 
set @delimitedIdentifier = QUOTENAME(@identifier, '"'); 

print @delimitedIdentifier; 
-- "Client""; DROP TABLE [dbo].Client; --" 

non ho trovato alcuna documentazione definitiva su come identificatori di escape citati in SQL Server. Ho trovato Delimited Identifiers (Database Engine) e ho anche visto this stackoverflow question sulla disinfezione.

Se fosse necessario chiamare la funzione QUOTENAME solo per citare gli identificatori che è molto traffico verso SQL Server che non dovrebbe essere necessario.

L'ORM sembra essere ben pensato per quanto riguarda SQL Injection. È in C# e precede la porta di NHibernate e Entity Framework ecc. Tutti gli input dell'utente vengono inviati utilizzando gli oggetti SqlParameter di ADO.NET, sono solo i nomi di identificatori di cui sono preoccupato in questa domanda. Questo bisogno di lavorare su SQL Server 2005 e 2008.


Aggiornamento 2010-03-31

Mentre l'applicazione non è supposto per permettere agli utenti di ingresso per i nomi di identificatori nelle query, l'ORM fa via la sintassi della query che ha per entrambe le letture in stile ORM e le query personalizzate. È l'ORM che sto cercando di prevenire in definitiva tutti i possibili attacchi di SQL Injection poiché è molto piccolo e facile da verificare rispetto a tutto il codice dell'applicazione.

Un semplice esempio di interfaccia di query:

session.Query(new TableReference("Client") 
    .Restrict(new FieldReference("city") == "Springfield") 
    .DropAllBut(new FieldReference("first_name")); 

ADO.NET invia tramite questa query:

exec sp_executesql N'SELECT "T1"."first_name" 
FROM "dbo"."Client" AS "T1" 
WHERE "T1"."city" = @p1;', 
N'@p1 nvarchar(30)', 
N'Springfield'; 

Forse sarebbe utile pensare a come qualcosa di simile questo potrebbe sembrare in NHibernate Query Language (HQL):

using (ISession session = NHibernateHelper.OpenSession()) 
{ 
    Client client = session 
     .CreateCriteria(typeof(Client)) \\ <-- TableReference in example above 
     .Add(Restrictions.Eq("city", "Springfield")) \\ <-- FieldReference above 
     .UniqueResult<Client>(); 
    return client; 
} 

Forse dovrei guardare e vedere come nibernare protegge t lui inserisce.

risposta

17

La funzione QuoteName deve controllare la lunghezza, poiché la funzione QUOTENAME T-SQL specifica la lunghezza massima restituita. Usando il tuo esempio:

String.Format(@"declare @delimitedIdentifier nvarchar(258); 
set @delimitedIdentifier = {0};", QuoteName(identifier)); 

Se QuoteName(identifier) è più lungo di 258 caratteri, sarà silenziosamente troncato quando viene assegnato a @delimitedIdentifier. Quando ciò accade, si apre la possibilità per @delimitedIdentifier di essere sfuggito in modo improprio.

C'è an MSDN article di Bala Neerumalla, uno "sviluppatore di software di sicurezza presso Microsoft", che spiega l'argomento in modo più approfondito. L'articolo contiene anche la cosa più vicina che ho trovato per "documentazione definitiva su come sfuggire identificatori tra virgolette in SQL Server":

Il meccanismo di fuga è semplicemente raddoppiando le occorrenze di parentesi quadre a destra. Non devi fare nulla con altri personaggi, comprese parentesi quadre a sinistra.

Questo è il codice C# Attualmente sto usando:

/// <summary> 
/// Returns a string with the delimiters added to make the input string 
/// a valid SQL Server delimited identifier. Brackets are used as the 
/// delimiter. Unlike the T-SQL version, an ArgumentException is thrown 
/// instead of returning a null for invalid arguments. 
/// </summary> 
/// <param name="name">sysname, limited to 128 characters.</param> 
/// <returns>An escaped identifier, no longer than 258 characters.</returns> 
public static string QuoteName(string name) { return QuoteName(name, '['); } 

/// <summary> 
/// Returns a string with the delimiters added to make the input string 
/// a valid SQL Server delimited identifier. Unlike the T-SQL version, 
/// an ArgumentException is thrown instead of returning a null for 
/// invalid arguments. 
/// </summary> 
/// <param name="name">sysname, limited to 128 characters.</param> 
/// <param name="quoteCharacter">Can be a single quotation mark ('), a 
/// left or right bracket ([]), or a double quotation mark (").</param> 
/// <returns>An escaped identifier, no longer than 258 characters.</returns> 
public static string QuoteName(string name, char quoteCharacter) { 
    name = name ?? String.Empty; 
    const int sysnameLength = 128; 
    if (name.Length > sysnameLength) { 
     throw new ArgumentException(String.Format(
      "name is longer than {0} characters", sysnameLength)); 
    } 
    switch (quoteCharacter) { 
     case '\'': 
      return String.Format("'{0}'", name.Replace("'", "''")); 
     case '"': 
      return String.Format("\"{0}\"", name.Replace("\"", "\"\"")); 
     case '[': 
     case ']': 
      return String.Format("[{0}]", name.Replace("]", "]]")); 
     default: 
      throw new ArgumentException(
       "quoteCharacter must be one of: ', \", [, or ]"); 
    } 
} 
+2

Penso tu voglia dire che dovrei richiedere che la lunghezza dell'input alla mia funzione sia <= 128 caratteri. Ciò ha senso. Il parametro della funzione QUOTENAME T-SQL è nvarchar (128) e restituisce nvarchar (258). Non riesco a vedere come possa mai restituire più di 258 caratteri da un input di 128 caratteri, perché sembra che al massimo ogni carattere sia raddoppiato (256), più un delimitatore di inizio e fine. Puoi dare un esempio di quando QUOTENAME potrebbe troncare? selezionare len (QUOTENAME (replicando (']', 128))), len (QUOTENAME (replicando (']', 129))) rendimenti: 258, null –

+0

Esattamente, la funzione è necessario verificare che il l'input è <= 128 caratteri. La stessa funzione QUOTENAME di T-SQL non tronca mai: il troncamento da tenere a mente sta assegnando il valore di ritorno della tua funzione C# QuoteName a una variabile nvarchar (258) in SQL (dinamico). Finché la tua funzione C# QuoteName richiede che l'input sia <= 128 caratteri, ad esempio si comporta come la funzione QUOTENAME di T-SQL, non hai nulla di cui preoccuparti. –

0

Non puoi semplicemente usare [e] delimitatori invece di virgolette (singole o doppie)?

identificatori dovrebbero mai contenere citazioni (a meno che non si è più sfortunato di adesso) in modo da rimuovere il normale fattore di utilizzo di citazioni nei nomi ecc

Edit:

Ma se le chiamate al ORM sono già parametrizzati, non devi preoccuparti di questo, no?L'utilizzo di [e] elimina la necessità di eseguire l'escape complesso in stringhe C#

+0

Quelli devono essere sfuggito pure. "Client]; DROP TABLE dbo.Client; -" dovrebbe essere sfuggito come [Client]]; DROP TABLE dbo.Client; -] I nomi degli oggetti reali nel database non contengono mai nulla di strano, è solo rendendo l'ORM protetto contro l'invio di query dannose. –

+1

Se tutto quello che possono passare è un nome identificativo, quindi prima di eseguire la stringa, non puoi controllare se l'identificatore è, in effetti, un oggetto? La tua stringa di iniezione ovviamente non supererà quella prova di annusata. –

+0

Grazie Aaron, non è una cattiva idea (e in effetti è già stata fatta per alcuni casi). Tuttavia, non voglio dover interrogare il database solo per vedere se l'oggetto esiste sempre. Posso mantenere una cache dei nomi degli oggetti in memoria per la maggior parte di essi, ma è abbastanza grande (circa 12.000 colonne, i nomi occupano 500KB di RAM - non un grosso problema ma ancora più di quanto mi piacerebbe). Apprezzo il suggerimento e sono aperto alle alternative, ma mi rendo conto di avere una base di codice esistente con cui lavorare e sto cercando una risposta alla domanda che ho posto. –