6

Czy jest coś jeszcze, co kod musi zrobić, aby oddzielić dane identyfikacyjne (tabela, widok, kolumna), inne niż zawijanie ich w cudzysłowy i podwójny cudzysłów znaki obecne w nazwie identyfikatora? Referencje byłyby mile widziane.Prawidłowe usuwanie identyfikatorów rozdzielonych w programie SQL Server bez użycia QUOTENAME

Odziedziczyłem bazę kodu, która ma niestandardowy system mapowania obiektowo-relacyjnego (ORM). SQL nie może być zapisany w aplikacji, ale ORM musi ostatecznie wygenerować SQL do wysłania do SQL Server. Wszystkie identyfikatory są cytowane podwójnymi cudzysłowami.

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

Gdybym budowy tego dynamicznego SQL w SQL, chciałbym skorzystać z wbudowanej funkcji 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; --" 

nie znalazłem żadnej ostatecznej dokumentacji na temat sposobu ukryte identyfikatory w SQL Server. Znalazłem Delimited Identifiers (Database Engine) i widziałem także this stackoverflow question o sanityzacji.

Jeśli musiałaby wywoływać funkcję QUOTENAME tylko po to, aby podawać identyfikatory o dużym natężeniu ruchu do SQL Server, które nie powinny być potrzebne.

ORM wydaje się być całkiem dobrze przemyślany pod względem SQL Injection. Jest w języku C# i poprzedza port nHibernate i Entity Framework itd. Wszystkie dane wejściowe użytkownika są wysyłane za pomocą obiektów SqlParameter ADO.NET, jest to tylko nazwa identyfikatora, o którą się martwię w tym pytaniu. To musi działać na SQL Server 2005 i 2008.


Aktualizacja 2010-03-31

Choć aplikacja nie ma umożliwić wejście użytkownika nazw identyfikatorów w zapytaniach, ORM czyni poprzez składnia zapytania, którą ma dla odczytów w stylu ORM i niestandardowych zapytań. To ORM staram się ostatecznie zapobiec wszystkim możliwym atakom SQL Injection, ponieważ jest to bardzo mały i łatwy do zweryfikowania w przeciwieństwie do całego kodu aplikacji.

Prostym przykładem interfejsu zapytań:

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

ADO.NET wysyła w ciągu tego zapytania:

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

Może to pomoże, aby myśleć o tym, jak coś podobnego mogłoby to wyglądać w NHibernate Query Język (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; 
} 

Może powinienem zobaczyć i zobaczyć, jak nHibernate chroni wprowadził.

Odpowiedz

17

Twoja funkcja QuoteName musi sprawdzić długość, ponieważ funkcja T-SQL QUOTENAME określa maksymalną długość, którą zwraca. Korzystanie przykład:

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

Jeśli QuoteName(identifier) jest dłuższa niż 258 znaków, to będzie cicho obcinane gdy przypisany do @delimitedIdentifier. Gdy tak się stanie, otworzysz możliwość niepoprawnego ucieczki w postaci @delimitedIdentifier.

Istnieje an MSDN article autorstwa Bala Neerumalla, "programisty oprogramowania zabezpieczającego w firmie Microsoft", które dokładniej wyjaśnia ten temat. Artykuł zawiera także najbliższą rzecz, jaką znalazłem w "ostatecznej dokumentacji dotyczącej unikania cytowanych identyfikatorów w SQL Server":

Mechanizm ucieczki po prostu podwaja występowanie prawego nawiasu kwadratowego. Nie musisz robić nic z innymi postaciami, w tym lewymi nawiasami kwadratowymi.

Jest to kod C# Obecnie używam:

/// <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

Myślę, że masz na myśli, że powinienem wymagać długości wejścia do mojej funkcji to <= 128 znaków. To ma sens. Parametr funkcji TOTAL QUOTENAME to nvarchar (128) i zwraca nvarchar (258). Nie widzę, aby mógł kiedykolwiek zwrócić więcej niż 258 znaków ze 128-znakowego wejścia, ponieważ wydaje się, że co najwyżej każdy znak byłby podwojony (256), a także początkowy i końcowy ogranicznik. Czy mógłbyś podać przykład, kiedy QUOTENAME zostałby obcięty? wybrać len (quotename (replikacji (']', 128))), len (quotename (replikacji (']', 129))) powraca: 258, null –

+0

Dokładnie, czynność musi potwierdzić, że wejście jest <= 128 znaków. Funkcja T-SQL QUOTENAME nigdy się nie skraca - obcięcie, na które trzeba uważać, przypisuje wartość zwracaną przez funkcję C# QuoteName do zmiennej nvarchar (258) w (dynamicznym) języku SQL. Tak długo, jak funkcja C# QuoteName wymaga, aby dane wejściowe było <= 128 znaków, tzn. Zachowuje się podobnie do funkcji T-SQL QUOTENAME, nie musisz się martwić. –

0

Czy nie można używać tylko ograniczników [i] zamiast cytatów (pojedynczych lub podwójnych)?

Identyfikatory naprawdę nigdy nie powinno zawierać żadnych cytatów (chyba, że ​​jesteś bardziej pechowy niż teraz), więc usunąć czynnik normalne używanie cytatów w nazwach itp

Edit:

Ale jeśli wezwań do ORM są już sparametryzowane, nie musisz się tym martwić, nie?Używanie [i] usuwa potrzebę złożonego ucieczki w łańcuchach C#

+0

Ci muszą być uciekł jak dobrze. "Klient"; DROP TABELA dbo.Client; - " musiałaby zostać usunięta jako [Klient]]; DROP TABLE dbo.Client; -] Rzeczywiste nazwy obiektów w bazie danych nie zawierają niczego dziwnego, a jedynie chronią ORM przed wysyłaniem złośliwych zapytań. –

+1

Jeśli wszystko, co mogą przekazać to nazwa identyfikatora, to przed wykonaniem łańcucha znaków, czy nie można sprawdzić, czy identyfikator jest w rzeczywistości obiektem? Twój ciąg wtryskowy oczywiście nie przejdzie testu wykrywania. –

+0

Dzięki Aaron, to nie jest zły pomysł (a tak naprawdę już jest w niektórych przypadkach). Nie chcę jednak przesyłać zapytań do bazy danych tylko po to, aby sprawdzić, czy obiekt istnieje cały czas. Mogę przechowywać pamięć podręczną nazw obiektów w pamięci dla większości z nich, ale to jest rodzaj dużego (około 12 000 kolumn, nazwy zajmują 500 KB pamięci RAM - nie jest to wielka sprawa, ale jeszcze więcej, niż bym chciał). Doceniam sugestię i jestem otwarty na alternatywy, ale zdaję sobie sprawę, że mam istniejącą podstawę kodu do pracy i szukam odpowiedzi na pytanie, które zadałem. –

Powiązane problemy