13

Przepraszamy za długie pytanie, ale zawiera ono cały SQL, którego użyłem do przetestowania scenariusza, aby mieć nadzieję, co do tego, co robię.Serwer SQL - dynamiczna tablica PIVOT - SQL Injection

Jestem zbudować jakiś dynamiczny SQL w celu uzyskania tabeli przestawnej w programie SQL Server 2005.

Poniżej jest kod, aby to zrobić. Z różnymi zaznaczeniami pokazującymi nieprzetworzone dane wartości przy użyciu GROUP BY i wartości w PIVOT, jak chcę.

BEGIN TRAN 
--Create the table 
CREATE TABLE #PivotTest 
(
    ColumnA nvarchar(500), 
    ColumnB nvarchar(500), 
    ColumnC int 
) 

--Populate the data 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('A', 'X', 1) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('A', 'Y', 2) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('A', 'Z', 3) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('A', 'X', 4) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('A', 'Y', 5) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('B', 'Z', 6) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('B', 'X', 7) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('B', 'Y', 8) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('B', 'Z', 9) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('C', 'X', 10) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('C', 'Y', 11) 
INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('C', 'Z', 12) 

--The data 
SELECT * FROM #PivotTest 

--Group BY 
SELECT 
    ColumnA, 
    ColumnB, 
    SUM(ColumnC) 
FROM 
    #PivotTest 
GROUP BY 
    ColumnA, 
    ColumnB 

--Manual PIVOT 
SELECT 
    * 
FROM 
    (
     SELECT 
      ColumnA, 
      ColumnB, 
      ColumnC 
     FROM 
      #PivotTest 
    ) DATA 
    PIVOT 
    (
     SUM(DATA.ColumnC) 
    FOR 
     ColumnB 
     IN 
     (
      [X],[Y],[Z] 
     ) 
    ) PVT 

--Dynamic PIVOT 
DECLARE @columns nvarchar(max) 

SELECT 
    @columns = 
    STUFF 
    (
     (
      SELECT DISTINCT 
       ', [' + ColumnB + ']' 
      FROM 
       #PivotTest 
      FOR XML PATH('') 
     ), 1, 1, '' 
    ) 

EXEC 
(' 
    SELECT 
     * 
    FROM 
     (
      SELECT 
       ColumnA, 
       ColumnB, 
       ColumnC 
      FROM 
       #PivotTest 
     ) DATA 
     PIVOT 
     (
      SUM(DATA.ColumnC) 
     FOR 
      ColumnB 
      IN 
      (
       ' + @columns + ' 
      ) 
     ) PVT 
') 

--The data again 
SELECT * FROM #PivotTest 

ROLLBACK 

Zawsze, gdy produkuję dynamiczny SQL, zawsze mam świadomość ataków SQL Injection. Dlatego dodałem następujący wiersz do innych instrukcji INSERT.

INSERT INTO #PivotTest (ColumnA, ColumnB, ColumnC) VALUES('A', 'FOO])) PVT; DROP TABLE #PivotTest;SELECT ((GETDATE()--', 1) 

Kiedy teraz uruchomić SQL, niskie i oto część EXEC spada zatem #PivotTest stół czyniąc ostatni SELECT niepowodzeniem.

Moje pytanie brzmi, czy ktoś wie o sposobie wykonywania dynamicznego PIVOT bez ryzyka ataków SQL Injection?

Odpowiedz

15

Wykonaliśmy wiele prac podobnych do twojego przykładu. Nie martwiliśmy się o injencję SQL, częściowo dlatego, że mamy pełną i całkowitą kontrolę nad obracanymi danymi - w żaden sposób szkodliwy kod nie może przedostać się przez ETL do naszej hurtowni danych.

Niektóre myśli i rada:

  • Czy wymagane do obracania z nvarcahr (500) kolumny? Nasze są varchar (25) lub numeryczne i byłoby dość trudno wymyślić tam szkodliwy kod.
  • Co z weryfikacją danych? Wydaje się, że jeśli jeden z tych ciągów zawierał znak "]", to albo próba włamania, albo dane, które i tak wybuchną na tobie.
  • Jak solidne jest twoje bezpieczeństwo? Czy system jest zablokowany w taki sposób, że Malorey nie może przekradać swoich włamań do bazy danych (bezpośrednio lub za pośrednictwem aplikacji)?

Hah. Zapamiętanie tej funkcji wymagało zapamiętania funkcji QUOTENAME(). Szybki test wydaje się wskazywać, że dodanie go do kodu jak to będzie działać (Dostaniesz błąd, a nie spadła tabeli temp):

SELECT 
     @columns = 
     STUFF 
     (
       (
         SELECT DISTINCT 
           ', [' + quotename(ColumnB, ']') + ']' 
         FROM 
           #PivotTest 
         FOR XML PATH('') 
       ), 1, 1, '' 
     ) 

to powinno działać na czopie (i UNPIVOT) sytuacjach, ponieważ prawie zawsze musisz [nawiązywać] swoje wartości.

+0

1) Moja próbka testowa jest prosta. Rzeczywiste kolumny to nvarchar (max). Nie mamy obecnie danych o takiej wielkości, a dane, które byłyby użyte w PIVOT rzadko sięgałyby nawet 100, więc mogę wykonać wymuszone obcięcie w tym przypadku! Świetny pomysł. 2) Myślałem o "[" i "]". Mam ochotę usunąć wszystkie nawiasy kwadratowe z danych i po prostu mam to jako ograniczenie tej funkcjonalności. 3) Jedynymi osobami, które mogą dodać te dane, są tzw. "Superużytkownicy", ale to nie wystarczy, aby zapewnić mi spokój. –

+1

QUOTENAME! Pierwszy raz widziałem to! Idealny! To całkowicie rozwiązuje problem. Dodałem QUOTES ręcznie.Jeśli usuniemy to i zrobię to za pomocą QUOTENAME, to wyłączy to SQL w tym polu! DZIĘKUJĘ CI! –

0

Trochę refaktoringu ...

CREATE PROCEDURE ExecutePivot (
    @TableName sysname, 
    @GroupingColumnName sysname, 
    @AggregateExpression VARCHAR(256), 
    @SelectExpression VARCHAR(256), 
    @TotalColumnName VARCHAR(256) = 'Total', 
    @DefaultNullValue VARCHAR(256) = NULL, 
    @IsExec BIT = 1) 
AS 
BEGIN 
    DECLARE @DistinctGroupedColumnsQuery VARCHAR(MAX); 
    SELECT @DistinctGroupedColumnsQuery = CONCAT('SELECT DISTINCT ',@GroupingColumnName,' FROM ',@TableName,';'); 
    DECLARE @DistinctGroupedColumnsResult TABLE ([row] VARCHAR(MAX)); 
    INSERT INTO @DistinctGroupedColumnsResult EXEC(@DistinctGroupedColumnsQuery); 

    DECLARE @GroupedColumns VARCHAR(MAX); 
    SELECT @GroupedColumns = STUFF ((SELECT DISTINCT CONCAT(', ',QUOTENAME([row])) FROM @DistinctGroupedColumnsResult FOR XML PATH('')), 1, 1, ''); 

    DECLARE @GroupedColumnsNullReplaced VARCHAR(MAX); 
    IF(@DefaultNullValue IS NOT NULL) 
     SELECT @GroupedColumnsNullReplaced = STUFF ((SELECT DISTINCT CONCAT(', ISNULL(',QUOTENAME([row]),',',@DefaultNullValue,') AS ',QUOTENAME([row])) FROM @DistinctGroupedColumnsResult FOR XML PATH('')), 1, 1, ''); 
    ELSE 
     SELECT @[email protected]; 

    DECLARE @ResultExpr VARCHAR(MAX) = CONCAT(' 
     ; WITH cte AS 
     (
      SELECT ',@SelectExpression,', ',@GroupedColumns,' 
      FROM ',@TableName,' 
      PIVOT (',@AggregateExpression,' FOR ',@GroupingColumnName,' IN (',@GroupedColumns,')) as p 
     ) 
     , cte2 AS 
     (
      SELECT ',@SelectExpression,', ',@GroupedColumnsNullReplaced,' 
      FROM cte 
     ) 
     SELECT ',@SelectExpression,', ',REPLACE(@GroupedColumns,',','+'),' AS ',@TotalColumnName,', ',@GroupedColumns,' 
     FROM cte2; 
     '); 

    IF(@IsExec = 1) EXEC(@ResultExpr); 
    ELSE SELECT @ResultExpr; 
END; 

Przykład użycia:

select schema_id, type_desc, 1 as Item 
    into PivotTest 
from sys.objects; 

EXEC ExecutePivot 'PivotTest','type_desc','SUM(Item)','schema_id','[Total Items]','0',1;