13

Ci scusiamo per la lunga domanda, ma questa contiene tutto l'SQL che ho usato per testare lo scenario per rendere sperabilmente chiaro ciò che sto facendo.SQL Server - Tabella PIVOT dinamica - SQL Injection

Sono costruire un po 'di SQL dinamico per produrre una tabella pivot in SQL Server 2005.

Qui di seguito è il codice per fare questo. Con varie selezioni che mostrano i dati grezzi i valori utilizzando GROUP BY e i valori in un PIVOT come li voglio.

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 

Ogni volta che produco qualsiasi SQL dinamico sono sempre a conoscenza degli attacchi SQL Injection. Pertanto ho aggiunto la seguente riga con le altre istruzioni INSERT.

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

Quando oggi ho eseguito lo SQL, bassa ed ecco, la parte EXEC gocce tavolo #PivotTest rendendo così l'ultimo SELEZIONA sicuro.

Quindi la mia domanda è, qualcuno sa di un modo per eseguire un PIVOT dinamico senza rischiare attacchi SQL Injection?

risposta

15

Abbiamo svolto un lavoro molto simile al tuo esempio. Non ci siamo preoccupati dell'ingiunzione SQL, in parte perché abbiamo il controllo totale e completo sui dati in rotazione - non c'è assolutamente modo che il codice dannoso possa passare attraverso ETL nel nostro data warehouse.

Alcune riflessioni e consigli:

  • State tenuti a ruotare con nvarcahr (500) le colonne? I nostri sono varchar (25) o numerici, e sarebbe piuttosto difficile introdurre del codice dannoso.
  • E il controllo dei dati? Sembra che se una di quelle stringhe contenesse un carattere "]", è un tentativo di hacker o di dati che ti esploderanno comunque.
  • Quanto è robusta la tua sicurezza? Il sistema è bloccato in modo tale che Malorey non può intrufolarsi nel suo database (direttamente o tramite la tua applicazione)?

Hah. È stato necessario scrivere tutto ciò per ricordare la funzione QUOTENAME(). Un test rapido sembrerebbe indicare che l'aggiunta al vostro codice in questo modo avrebbe funzionato (Si otterrà un errore, non una tabella temporanea caduto):

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

Questo dovrebbe funzionare per perno (e UNPIVOT) situazioni, dal momento che quasi sempre devi [parentesi] i tuoi valori.

+0

1) Il mio campione è semplice. Le colonne effettive sono nvarchar (max). Al momento non disponiamo di dati di dimensioni e i dati che verrebbero utilizzati per il PIVOT saranno raramente pari a 100, quindi in questo caso potrei eseguire un troncamento forzato! Grande idea. 2) Stavo pensando al '[' e ']'. Sono tentato di togliere tutte le parentesi quadre dai dati e limitarmi a questa funzionalità. 3) Le uniche persone che possono aggiungere questi dati sono i cosiddetti "Super utenti", tuttavia, questo non è sufficiente per darmi tranquillità. –

+1

QUOTENAME! La prima volta che l'ho visto! Perfezionare! Questo risolve completamente il problema. Stavo aggiungendo le QUOTE manualmente.Se rimuovo questo e lo faccio usando QUOTENAME, disabiliterà qualsiasi SQL all'interno di quel campo! GRAZIE! –

0

Un po 'di refactoring ...

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; 

Esempio di utilizzo:

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;