SQL Server - dynamiczna tabela przestawna - SQL Injection

Przepraszam za długie pytanie, ale to zawiera cały SQL, którego użyłem do przetestowania scenariusza, aby miejmy nadzieję wyjaśnić, co robię.

Buduję dynamiczny SQL, aby stworzyć tabelę przestawną w SQL Server 2005.

Poniżej znajduje się kod, aby to zrobić. Z różnymi wyborami pokazującymi surowe dane wartości za pomocą GROUP BY i wartości w przegubie, jak chcę je.

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

Za każdym razem, gdy tworzę dynamiczny SQL, zawsze jestem świadomy ataków SQL Injection. Dlatego mam dodano następujący wiersz z innymi instrukcjami INSERT.

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

Kiedy teraz uruchamiam SQL, low I oto, część EXEC spada tabelę # PivotTest, co powoduje, że ostatni SELECT nie powiedzie się.

Więc moje pytanie brzmi, czy ktoś zna sposób, aby wykonać dynamiczny PIVOT bez ryzyka ataków SQL Injection?

Author: Robin Day, 2009-09-17

3 answers

Wykonaliśmy wiele pracy podobnej do twojego przykładu. Nie martwiliśmy się o Wtrysk SQL, po części dlatego, że mamy pełną i całkowitą kontrolę nad zmienianymi danymi-po prostu nie ma mowy, aby złośliwy kod mógł przedostać się przez ETL do naszej hurtowni danych.

Kilka myśli i Rad:

  • czy wymagane jest obracanie kolumnami nvarcahr(500)? Nasze są varchar (25) lub numerics, i byłoby dość trudno przemycić szkodliwy kod TAM.
  • a co z danymi sprawdzasz? Wygląda na to, że jeśli jeden z tych ciągów zawierał znak"]", jest to albo próba włamania, albo dane, które i tak wybuchną na Ciebie.
  • Jak solidne jest Twoje bezpieczeństwo? Czy system jest tak zablokowany, że Malorey nie może przemycić swoich hacków do twojej bazy danych (bezpośrednio lub za pośrednictwem aplikacji)?
Hah. Zapisanie tego wszystkiego wymagało zapamiętania funkcji QUENAME (). Szybki test wydaje się wskazywać, że dodanie go do kodu tak by zadziałało (pojawi się błąd, "not a dropped temp table"): {]}
SELECT
        @columns = 
        STUFF
        (
                (
                        SELECT DISTINCT
                                ', [' + quotename(ColumnB, ']') + ']'
                        FROM
                                #PivotTest
                        FOR XML PATH('')
                ), 1, 1, ''
        )

Powinno to działać w sytuacjach pivot( i unpivot), ponieważ prawie zawsze musisz [wspornik] swoich wartości.

 15
Author: Philip Kelley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-17 15:49:09

Trochę refaktoryzacji...

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 @GroupedColumnsNullReplaced=@GroupedColumns;

    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;
 0
Author: Abin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-02 12:45:31
DECLARE @PvtColumns varchar(max)

SET @PvtColumns = STUFF((SELECT ',MAX(CASE WHEN Seq = ' + CAST(Seq AS varchar(10)) + ' THEN gr_hdr_grno END) AS grNo_' +  CAST(Seq AS varchar(10)) 
+',MAX(CASE WHEN Seq = ' + CAST(Seq AS varchar(10)) + ' THEN gr_hdr_docvalue END) AS gramt_' +  CAST(Seq AS varchar(10))
+',MAX(CASE WHEN Seq = ' + CAST(Seq AS varchar(10)) + ' THEN gr_tcd_amt END) AS grtcd_' +  CAST(Seq AS varchar(10)) 
+',MAX(CASE WHEN Seq = ' + CAST(Seq AS varchar(10)) + ' THEN document_no END) AS sobi_' +  CAST(Seq AS varchar(10))
+',MAX(CASE WHEN Seq = ' + CAST(Seq AS varchar(10)) + ' THEN sobiamount END) AS samt_' +  CAST(Seq AS varchar(10))
+',MAX(CASE WHEN Seq = ' + CAST(Seq AS varchar(10)) + ' THEN sobivat END) AS svat_' +  CAST(Seq AS varchar(10))
FROM (SELECT DISTINCT Seq FROM (SELECT ROW_NUMBER() OVER (PARTITION BY pomas_pono ORDER BY pomas_pono) AS Seq
FROM po_grn_vat_supp)t)r
ORDER BY Seq
FOR XML PATH('')),1,1,'')


DECLARE @SQL varchar(max) = 'SELECT supp_spmn_supcode,supp_spmn_supname,supp_bu_language,vatregno,pomas_pono,pomas_pobasicvalue,pomas_tcdtotalrate,' + @PvtColumns + ' 
FROM  (SELECT ROW_NUMBER() OVER (PARTITION BY pomas_pono ORDER BY pomas_pono) AS Seq,*
FROM po_grn_vat_supp)t GROUP BY supp_spmn_supcode,supp_spmn_supname,supp_bu_language,vatregno,pomas_pono,pomas_pobasicvalue,pomas_tcdtotalrate'

EXEC (@SQL)
 -1
Author: Mohamed Udhuman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-26 13:28:20