13

Désolé pour la longue question, mais celle-ci contient tout le SQL que j'ai utilisé pour tester le scénario afin d'éclaircir ce que je fais.SQL Server - Table dynamique PIVOT - Injection SQL

Je suis construire une SQL dynamique pour produire un tableau croisé dynamique dans SQL Server 2005.

est le code ci-dessous pour le faire. Avec diverses sélections montrant les données brutes les valeurs en utilisant GROUP BY et les valeurs dans un PIVOT comme je les veux.

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 

Chaque fois que je produis un SQL dynamique, je suis toujours au courant des attaques par injection SQL. J'ai donc ajouté la ligne suivante avec les autres instructions INSERT.

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

Quand je lance maintenant le SQL, faible et voici, la partie EXEC supprime la table #PivotTest rendant ainsi la dernière SELECT échouer. Donc, ma question est, est-ce que quelqu'un connaît un moyen d'effectuer un PIVOT dynamique sans risquer des attaques par injection SQL?

Répondre

15

Nous avons fait beaucoup de travail similaire à votre exemple. Nous ne nous sommes pas inquiétés de l'injonction SQL, en partie parce que nous avons un contrôle complet et total sur les données pivotées - il n'y a tout simplement pas de code malveillant qui pourrait passer par ETL dans notre entrepôt de données.

Quelques réflexions et conseils:

  • Devez-vous faire pivoter avec nvarcahr (500) colonnes? Les nôtres sont des varchar (25) ou des numériques, et il serait assez difficile d'infiltrer le code dommageable par là.
  • Qu'en est-il de la vérification des données? On dirait que si l'une de ces chaînes contenait un caractère "]", c'est soit une tentative de piratage, soit des données qui vous exploseraient de toute façon.
  • Quelle est la robustesse de votre sécurité? Le système est-il verrouillé de telle sorte que Malorey ne peut pas faire entrer ses hacks dans votre base de données (directement ou via votre application)?

Hah. Il a fallu écrire tout ça pour se souvenir de la fonction QUOTENAME(). Un test rapide semble indiquer que l'ajouter à votre code comme ceci fonctionnerait (Vous obtiendrez une erreur, pas une table temporaire a chuté):

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

Cela devrait fonctionner pour les situations pivot (et UNPIVOT), puisque vous devez presque toujours [encadrer] vos valeurs.

+0

1) Mon échantillon de test est simple. Les colonnes réelles sont nvarchar (max). Nous n'avons pas de données de cette taille à l'heure actuelle et les données qui seraient utilisées pour le PIVOT seraient rarement autant que 100 donc je peux effectuer une troncature forcée dans ce cas! Bonne idée. 2) Je pensais aux '[' et ']'. Je suis tenté de supprimer tous les crochets des données et de limiter cela à cette fonctionnalité. 3) Les seules personnes pouvant ajouter ces données sont les "super-utilisateurs", mais cela ne me suffit pas pour avoir l'esprit tranquille. –

+1

QUOTENAME! La première fois que je l'ai vu! Parfait! Cela résout complètement le problème. J'ajoutais les CITATIONS manuellement.Si je supprime ceci et le fais en utilisant QUOTENAME cela désactivera tout SQL dans ce champ! JE VOUS REMERCIE! –

0

Un peu de 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; 

Exemple d'utilisation:

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;