Quali sono i pericoli dell'SQL dinamico e possono essere evitati?
-
06-07-2019 - |
Domanda
Abbiamo appena ricevuto il seguente codice come soluzione per una query di ricerca complicata in una nuova applicazione fornita da sviluppatori offshore. Sono scettico sull'uso di SQL dinamico perché potrei chiudere l'istruzione SQL usando '; e poi scusa un brutto che verrà eseguito sul database!
Qualche idea su come correggere l'attacco di iniezione?
ALTER procedure [dbo].[SearchVenues] --'','',10,1,1,''
@selectedFeature as varchar(MAX),
@searchStr as varchar(100),
@pageCount as int,
@startIndex as int,
@searchId as int,
@venueName as varchar(100),
@range int,
@latitude varchar(100),
@longitude varchar(100),
@showAll int,
@OrderBy varchar(50),
@SearchOrder varchar(10)
AS
DECLARE @sqlRowNum as varchar(max)
DECLARE @sqlRowNumWhere as varchar(max)
DECLARE @withFunction as varchar(max)
DECLARE @withFunction1 as varchar(max)
DECLARE @endIndex as int
SET @endIndex = @startIndex + @pageCount -1
SET @sqlRowNum = ' SELECT Row_Number() OVER (ORDER BY '
IF @OrderBy = 'Distance'
SET @sqlRowNum = @sqlRowNum + 'dbo.GeocodeDistanceMiles(Latitude,Longitude,' + @latitude + ',' + @longitude + ') ' +@SearchOrder
ELSE
SET @sqlRowNum = @sqlRowNum + @OrderBy + ' '+ @SearchOrder
SET @sqlRowNum = @sqlRowNum + ' ) AS RowNumber,ID,RecordId,EliteStatus,Name,Description,
Address,TotalReviews,AverageFacilityRating,AverageServiceRating,Address1,Address2,Address3,Address4,Address5,Address6,PhoneNumber,
visitCount,referalCount,requestCount,imgUrl,Latitude,Longitude,
Convert(decimal(10,2),dbo.GeocodeDistanceMiles(Latitude,Longitude,' + @latitude + ',' + @longitude + ')) as distance
FROM VenueAllData '
SET @sqlRowNumWhere = 'where Enabled=1 and EliteStatus <> 3 '
--PRINT('@sqlRowNum ='+@sqlRowNum)
IF @searchStr <> ''
BEGIN
IF (@searchId = 1) -- county search
BEGIN
SET @sqlRowNumWhere = @sqlRowNumWhere + ' and Address5 like ''' + @searchStr + '%'''
END
ELSE IF(@searchId = 2 ) -- Town search
BEGIN
SET @sqlRowNumWhere = @sqlRowNumWhere + ' and Address4 like ''' + @searchStr + '%'''
END
ELSE IF(@searchId = 3 ) -- postcode search
BEGIN
SET @sqlRowNumWhere = @sqlRowNumWhere + ' and Address6 like ''' + @searchStr + '%'''
END
IF (@searchId = 4) -- Search By Name
BEGIN
IF @venueName <> ''
SET @sqlRowNumWhere = @sqlRowNumWhere + ' and ( Name like ''%' + @venueName + '%'' OR Address like ''%'+ @venueName+'%'' ) '
ELSE
SET @sqlRowNumWhere = @sqlRowNumWhere + ' and ( Name like ''%' + @searchStr + '%'' OR Address like ''%'+ @searchStr+'%'' ) '
END
END
IF @venueName <> '' AND @searchId <> 4
SET @sqlRowNumWhere = @sqlRowNumWhere + ' and ( Name like ''%' + @venueName + '%'' OR Address like ''%'+ @venueName+'%'' ) '
set @sqlRowNum = @sqlRowNum + ' ' + @sqlRowNumWhere
--PRINT(@sqlRowNum)
IF @selectedFeature <> ''
BEGIN
DECLARE @val1 varchar (255)
Declare @SQLAttributes varchar(max)
Set @SQLAttributes = ''
Declare @tempAttribute varchar(max)
Declare @AttrId int
while (@selectedFeature <> '')
BEGIN
SET @AttrId = CAST(SUBSTRING(@selectedFeature,1,CHARINDEX(',',@selectedFeature)-1) AS Int)
Select @tempAttribute = ColumnName from Attribute where id = @AttrId
SET @selectedFeature = SUBSTRING(@selectedFeature,len(@AttrId)+2,len(@selectedFeature))
SET @SQLAttributes = @SQLAttributes + ' ' + @tempAttribute + ' = 1 And '
END
Set @SQLAttributes = SUBSTRING(@SQLAttributes,0,LEN(@SQLAttributes)-3)
set @sqlRowNum = @sqlRowNum + ' and ID in (Select VenueId from '
set @sqlRowNum = @sqlRowNum + ' CachedVenueAttributes WHERE ' + @SQLAttributes + ') '
END
IF @showAll <> 1
set @sqlRowNum = @sqlRowNum + ' and dbo.GeocodeDistanceMiles(Latitude,Longitude,' + @latitude + ',' + @longitude + ') <= ' + convert(varchar,@range )
set @withFunction = 'WITH LogEntries AS (' + @sqlRowNum + ')
SELECT * FROM LogEntries WHERE RowNumber between '+ Convert(varchar,@startIndex) +
' and ' + Convert(varchar,@endIndex) + ' ORDER BY ' + @OrderBy + ' ' + @SearchOrder
print(@withFunction)
exec(@withFunction)
Soluzione
A parte, non vorrei usare EXEC
; preferirei usare sp_executesql
. Vedi questo fantastico articolo, The Curse and Blessings of Dynamic SQL , per la ragione e altre informazioni su usando sql dinamico.
Altri suggerimenti
Ecco una versione ottimizzata della query sopra che non utilizza SQL dinamico ...
Declare @selectedFeature as varchar(MAX),
@searchStr as varchar(100),
@pageCount as int,
@startIndex as int,
@searchId as int,
@venueName as varchar(100),
@range int,
@latitude varchar(100),
@longitude varchar(100),
@showAll int,
@OrderBy varchar(50),
@SearchOrder varchar(10)
Set @startIndex = 1
Set @pageCount = 50
Set @searchStr = 'e'
Set @searchId = 4
Set @OrderBy = 'Address1'
Set @showAll = 1
--Select dbo.GeocodeDistanceMiles(Latitude,Longitude,@latitude,@longitude)
DECLARE @endIndex int
SET @endIndex = @startIndex + @pageCount -1
;
WITH LogEntries as (
SELECT
Row_Number()
OVER (ORDER BY
CASE @OrderBy
WHEN 'Distance' THEN Cast(dbo.GeocodeDistanceMiles(Latitude,Longitude,@latitude,@longitude) as varchar(10))
WHEN 'Name' THEN Name
WHEN 'Address1' THEN Address1
WHEN 'RecordId' THEN Cast(RecordId as varchar(10))
WHEN 'EliteStatus' THEN Cast(EliteStatus as varchar(10))
END) AS RowNumber,
RecordId,EliteStatus,Name,Description,
Address,TotalReviews,AverageFacilityRating,AverageServiceRating,Address1,Address2,Address3,Address4,Address5,Address6,PhoneNumber,
visitCount,referalCount,requestCount,imgUrl,Latitude,Longitude,
Convert(decimal(10,2),dbo.GeocodeDistanceMiles(Latitude,Longitude,@latitude,@longitude)) as distance
FROM VenueAllData
where Enabled=1 and EliteStatus <> 3
And
(
(Address5 like @searchStr + '%' And @searchId = 1) OR
(Address4 like @searchStr + '%' And @searchId = 2) OR
(Address6 like @searchStr + '%' And @searchId = 3) OR
(
(
@searchId = 4 And
(Name like '%' + @venueName + '%' OR Address like '%'+ @searchStr+'%')
)
)
)
And
ID in (
Select VenueID
From CachedVenueAttributes
--Extra Where Clause for the processing of VenueAttributes using @selectedFeature
)
And
(
(@showAll = 1) Or
(@showAll <> 1 and dbo.GeocodeDistanceMiles(Latitude,Longitude,@latitude,@longitude) <= convert(varchar,@range ))
)
)
SELECT * FROM LogEntries
WHERE RowNumber between @startIndex and @endIndex
ORDER BY CASE @OrderBy
WHEN 'Distance' THEN Cast(Distance as varchar(10))
WHEN 'Name' THEN Name
WHEN 'Address1' THEN Address1
WHEN 'RecordId' THEN Cast(RecordId as varchar(10))
WHEN 'EliteStatus' THEN Cast(EliteStatus as varchar(10))
END
L'unica cosa che non ho risolto è la selezione da CachedVenueAttributes che sembra creare un'istruzione where in un ciclo. Penso che potrei metterlo in una funzione valutata a livello di tabella e riformularlo in isolamento rispetto al resto della procedura.
Mi piace l'SQL dinamico per la ricerca.
Dove l'ho usato in passato ho usato istruzioni preparate .Net con qualsiasi stringa generata dall'utente passata come parametro NON incluso come testo nell'SQL.
Per funzionare con la soluzione esistente è possibile fare una serie di cose per mitigare il rischio.
- Input di white list, convalidare input in modo che possa contenere solo a-zA-Z0-9 \ w (numeri alfa e spazi bianchi) (non valido se è necessario supportare caratteri unicode)
- Esegue qualsiasi sql dinamico come utente limitato. Impostare il proprietario del proc memorizzato su un utente che ha accesso in sola lettura alle tabelle interessate. negare di scrivere a tutte le tabelle ect. Inoltre, quando si chiama questo proc memorizzato, potrebbe essere necessario farlo con un utente con restrizioni simili su ciò che può fare, poiché sembra che MS-SQL esegua sql dinamico all'interno di un processo memorizzato in quanto l'utente chiamante non è il proprietario del processo memorizzato.
Ho capito che questo è un post molto vecchio, ma quando faccio cose come:
AND
(
(@showAll = 1)
OR (@showAll <> 1
AND dbo.GeocodeDistanceMiles(Latitude,Longitude,@latitude,@longitude) <= convert(varchar,@range))
)
... un OPTION (RECOMPILE)
di solito ti aiuterà a scegliere un piano più conciso, a condizione che non venga eseguito mille volte al secondo o altro.