Det verkar ofta att webbplatser existerar för att sätta något i en databas för att dra ut det senare. Medan andra databasmetoder, till exempel NoSQL, har blivit populära de senaste åren, finns data för många webbplatser fortfarande kvar i den traditionella SQL-databasen. Dessa uppgifter består ofta av värdefull personlig information som kreditkortsnummer och annan personlig information av intresse för identitetstjuvar och brottslingar. Hackare ser därför alltid ut att få dessa uppgifter. Ett av de vanligaste målen för dessa attacker är SQL-databaser som ligger bakom många webbapplikationer genom en process med SQL Injection.
En injektionsattack fungerar genom att få ansökan att skicka otillförlitlig inmatning till tolken. Nyligen är 40 000 kundrekord som tas från Bell Canada ett resultat av en SQL-injektionsattack. I slutet av 2013 stal hackare över $ 100 000 från en Kalifornien-baserad ISP med hjälp av SQL-injektion.
Säkerhetsprojektet Open Web Application (OWASP) valde injektionsangreppet som den första applikationssäkerhetsrisken i topp tio 2013, baserat på prevalensen och risken för de attackerade webbsystemen. Tyvärr höll det också nummer ett i den tidigare rapporten från 2010. Så vad är en SQL-injektionsattack? I denna handledning diskuterar jag hur de fungerar och vad du kan göra för att skydda din ansökan från dessa attacker.
Varje tolkat system bakom en webbserver kan vara målet för en injektionsattack. De vanligaste målen är SQL-databasservrarna bakom många webbplatser. SQL-injektion beror inte direkt på svagheter i databasen, men använder öppningar i programmet för att tillåta angriparen att utföra uttalanden om angriparens val på servern. En SQL-injektionsattack får databasen att dela mer information än applikationen är avsedd att tillhandahålla. Låt oss titta på ett SQL-databassamtal som du kan skriva i ASP.NET.
SqlCommand command = new SqlCommand ("SELECT * FROM userdata WHERE UserId =" + id); SqlDataReader reader = command.ExecuteReader ();
Vad är fel med den här koden? Kanske ingenting. Frågan är det id
sträng vi använder. Var kommer det här värdet från? Om vi genererar det internt eller från en betrodd källa, kan den här koden fungera utan problem. Men om vi får värdet från en användare och använder utan ändringar, har vi just öppnat oss för SQL-injektion.
Låt oss ta ett vanligt fall där vi får parametern att leta upp som en del av webbadressen. Ta webbadressen http://www.example.com/user/details?id=123
. I ASP.NET med C # kan vi få det godkända värdet med den här koden:
sträng id = Request.QueryString ["id"];
Denna kod kombinerad med samtalet ovan lämnar oss öppna för en attack. Om användaren skickar ett användar-ID som 123 som förväntat fungerar det bra. Men inget som vi gjort här ser till att detta är fallet. Låt oss säga att angriparen försöker komma åt webbadressen http://www.example.com/user/details?id=0; VÄLJ * FRÅN userdata
.
Istället för att bara överföra ett värde som förväntat, har vi givit ett värde och sedan lagt till en semikolon, som avslutar ett SQL-uttalande. Det lägger sedan till ett andra SQL-meddelande som angriparen skulle vilja köra. I det här fallet skulle det returnera alla poster i userdata tabellen. Beroende på resten av koden på vår sida kan det returnera ett fel eller kanske visa varje post i databasen till angriparen. Även ett fel kan användas med noggrant konstruerade frågor för att bygga upp en bild av databasen. Värre, tänk att angriparen går till en URL till http://www.example.com/user/details?id=0; DROP TABLE userdata
. Nu är alla dina användare förlorade.
I det här exemplet kan angriparen köra vilken kod som helst på databasservern. Om det konto som databassamtal körs har fullständig kontroll över databasen, ett alltför vanligt scenario, så släpper tabeller och raderar poster gör det enkelt att ta ner en webbplats. Även om angriparen endast kan läsa och skriva data i databasen är pulsdata bara en fråga om tålamod och vård.
Ta en enkel databas med namnet "Produkter" bestående av tre kolumner. Den första kolumnen innehåller produkt-id, den andra håller produktnamnet och den tredje håller produktens pris. För vår vanliga fråga försöker vi hitta alla produkter som innehåller widgeten i namnet. SQL för att göra detta i samma mönster som vi visar skulle se ut så här:
sträng sql = "VÄLJ * FRÅN PRODUKTER WHERE produktnamn LIKE '%" + sökterm + "%" ";
En typisk webbplats för att komma åt detta skulle se ut http://www.example.com/product?search=widget
. Här kommer webbsidan helt enkelt att cykla genom varje återkommande post och visa den på skärmen. I det här fallet ser vi vår widget produkt.
| productid | produktnamn | pris | | ----------- | 1 | Widget | 100,00 |
Låt oss ändra vår fråga till http://www.example.com/product?search=widget 'ELLER 1 = 1;--
och kör samma fråga. se något mer. Faktum är att vi ser varje post i tabellen.
| productid | produktnamn | pris | | --- | 1 | Widget | 100,00 | | 2 | Thingy | 50,00 | | 3 | Boxy | 125,00 |
Vi har lurat tolken i att springa SQL-kod efter eget val. Den resulterande SQL-exekveringen kommer att vara:
VÄLJ * FRÅN PRODUKTER WHERE produktnamn LIKE '% widget' ELLER 1 = 1; -% '
Resultatet kommer att vara två uttalanden:
VÄLJ * FRÅN PRODUKTER VAR produktnamn LIKE '% widget' ELLER 1 = 1; -%'
Genom att lägga till 1 = 1
, vilket alltid är sant, var klausulen kommer att vara sant för varje rad i bordet och den resulterande frågan kommer tillbaka varje rad. De --
i början av det andra uttalandet gör resten av SQL-satsen till en kommentar som förhindrar felmeddelandet som vi annars skulle se.
Ändringar på skärmen kan inte förhindra detta uttalande. En patientangripare kan inte använda något annat än det faktum att ett värde returneras eller inte och noggrant konstruerade frågor för att långsamt kartlägga din databas och eventuellt hämta data även om de bara ser ett felmeddelande. Det finns verktyg som sqlmap för att automatisera processen.
Du förhindrar SQL-injektion genom att förhindra otillförlitlig inmatning från att komma till SQL-databasen eller annan tolk. Varje inmatning från utsidan av systemet bör anses vara osäker. Även data från andra partnersystem måste betraktas som osäker eftersom du inte har något sätt att garantera att det andra systemet inte lider av säkerhetsproblem som skulle möjliggöra införande av godtyckliga data och skickas vidare till din ansökan.
Gå tillbaka till vårt tidigare exempel, om vi vet att id-parametern alltid ska vara ett heltal, kan vi försöka konvertera det till ett heltal och visa ett fel om det misslyckas. En ASP.NET MVC-applikation skulle göra detta med hjälp av kod som liknar detta:
int kvantitet; om (! int.TryParse (Request.QueryString ["qty"], ut kvantitet)) returnera RedirectToAction ("Invalid");
Detta kommer att försöka konvertera strängen till ett heltal. Om konverteringen misslyckas, omdirigerar koden till en åtgärd som visar ett ogiltigt meddelande.
Detta kan förhindras om vi letar efter en heltalsparameter. Det skulle inte hjälpa om vi förväntar oss text som i den tidigare produktsökningen. Den föredragna tekniken i detta fall är att använda reguljära uttryck eller strängbyte för att tillåta endast erforderliga tecken i det godkända värdet.
Detta kan göras med vitlistning, processen att ta bort alla tecken annat än en specificerad uppsättning eller svartlistning, processen att ta bort en medlem av en specificerad uppsättning från strängen. Whitelisting är mer tillförlitlig eftersom du anger endast tillåtna tecken. För att ta bort något annat än bokstäver och siffror i en sträng kan vi använda kod som:
Regex regEx = ny Regex ("[^ a-zA-Z0-9 -]"); sträng filteredString = regEx (originalString, "");
Du måste utvärdera vilken ingång som helst. Vissa databasfrågor kan behöva mer specialiserad uppmärksamhet. Ta tecken som kan vara meningsfulla i ett SQL-kommando, men kan också vara ett giltigt tecken i ett databassamtal. Till exempel används det enda citatteckenet för att starta och avsluta en sträng i SQL, men kan också vara en del av en persons namn som O'Conner. I det här fallet ersätter det enda citatet '
med konsekutiva enda citat "
kan eliminera problemet.
Lagrade procedurer ses ofta som åtgärden för detta problem och de kan vara en del av lösningen. Men en dåligt skrivet lagrad procedur sparar inte dig. Ta den här lagrade proceduren som innehåller kod som liknar vad vi redan har sett för att bygga en fråga:
ALTER PROCEDURE [dbo]. [SearchProducts] @searchterm VARCHAR (50) = "AS BEGIN DECLARE @query VARCHAR (100) SET @query = 'VÄLJ * FRÅN Produkter WHERE produktnamn LIKE"% "+ @searchterm +'%"; EXEC (@query) END
Strängkonstruktionen här är problemet. Låt oss försöka ett enkelt försök där vi försöker ställa ett alltid sant tillstånd för att visa alla rader i tabellen och gå över i samma "widget" ELLER 1 = 1; - "som den fråga vi såg tidigare. Resultatet är också detsamma :
| productid | produktnamn | pris | | - | 1 | Widget | 100,00 | | 2 | Thingy | 50,00 | | 3 | Boxy | 125,00 |
Om otillförlitliga data skickas in, har vi samma resultat som om samtalet skapades i vår kod. Att strängkonstruktionen sker i en lagrad procedur istället för i vår kod ger inget skydd.
Nästa pusselbitar som skyddar mot injektionsattacker kommer in med parametrering. Att bygga SQL-frågor genom att sammanfoga strängar och sedan passera den färdiga koden ger inte databasen någon aning om vilken del av strängen som är en parameter och vad är en del av kommandot. Vi kan hjälpa till att skydda mot attacker genom att skapa SQL-samtal på ett sätt som håller uttalanden och värden åtskilda.
Vi kan skriva om det lagrade förfarandet som visats tidigare för att använda parametrar och producera ett säkrare samtal. Istället för att sammanfoga %
tecken som representerar jokertecken skapar vi en ny sträng som lägger till dessa tecken och skickar sedan den här nya strängen som en parameter till SQL-satsen. Den nya lagrade proceduren ser ut så här:
ALTER PROCEDURE [dbo]. [SearchProductsFixed] @searchterm NVARCHAR (50) = "AS BEGIN DECLARE @query NVARCHAR (100) DECLARE @msearch NVARCHAR (55) SET @msearch = '%' + @searchterm + '%' SET @query = 'VÄLJ * FRÅN PRODUKTER WHERE produktnamn LIKE @search' EXEC sp_executesql @query, N '@ search VARCHAR (55)', @msearch END
Att utföra den här lagrade proceduren med bara orddisplayen fungerar som förväntat.
| productid | produktnamn | pris | ---- | 1 | Widget | 100,00
Och om vi skickar med vår parameter widget "OR 1 = 1; - Resultatet att inget returneras visar att vi inte längre är sårbara för denna attack.
Parameterisering kräver inte lagrade procedurer. Du kan också dra nytta av det med frågor som byggts inom koden. Här är ett kort segment i C # för att ansluta och köra en fråga mot Microsoft SQL-servern med en parameter.
const string sql = "SELECT * FRÅN Produkter WHERE produktnamn LIKE @CategoryID"; var connString = WebConfigurationManager.ConnectionStrings ["ProductDatabase"]. ConnectionString; använder (var conn = ny SqlConnection (connString)) var kommando = ny SqlCommand (sql, conn); command.Parameters.Add ("@ searchterm", SqlDbType.NVarChar) .Value = string.Format ("% 0%", searchTerm); command.Connection.Open (); SqlDataReader reader = command.ExecuteReader (); medan (läsare.NextResult ()) // Kod som ska utföras på varje rad går här
Hittills har jag visat hur man kan mildra databasattacker. Ett annat viktigt lag av försvar minimerar skadan orsakar om angriparen kommer förbi de andra försvaren. Begreppet minsta privilegium ger den en kodmodul, som i detta fall vår databas samlar, endast får tillgång till information och resurser som behövs för dess ändamål.
När ett databaskommando körs gör det det under ett användarkonto. Vi får säkerhet genom att ge kontot som databassamtal endast gäller rättigheter att göra saker som normalt behöver göras. Om samtal från en databas endast bör läsa data från ett bord, ge bara kontots valda rättigheter till tabellen och inte infoga eller ta bort. Om det finns specifika tabeller behöver det uppdateras, säg en ordertabell, sedan ge den in och uppdatera till den tabellen, men inte någon annan tabell, som en som innehåller användarinformation.
Avbeställning av beställningar kan hanteras via ett separat konto som bara används på sidan som utför den uppgiften. Detta skulle förhindra att en order från bordet skulle bli svårare på annat håll. Använda ett separat databaskonto för webbplatsens administrativa funktioner med nödvändiga större rättigheter än den offentliga användning kan göra mycket för att förhindra skador från en användare som finner ett öppet injektionsattack.
Detta hindrar inte alla attacker. Det skulle inte göra något för att förhindra återkommande extra resultat, såsom det tidigare exemplet som visar hela innehållet i ett bord. Det skulle förhindra attacker från att uppdatera eller radera data.
SQL-injektion är den farligaste attacken, särskilt när man tar hänsyn till hur sårbara webbplatser är till det och hur mycket potential den här typen av attack har orsakat mycket skada. Här har jag beskrivit SQL-injektionsattacker och visat den skada man kan göra. Lyckligtvis är det inte så svårt att skydda dina webbprojekt från denna sårbarhet genom att följa några enkla regler.
Lita aldrig på externa data som tagits in i din ansökan. Den bör valideras mot en vitlista med giltiga ingångar innan det behandlas ytterligare. Detta kan innebära att en heltalsparameter faktiskt är ett heltal eller ett datum är ett giltigt datumvärde. Validera även text för att bara inkludera de tecken som parametern skulle behöva. För en textsökning kan du ofta endast tillåta bokstäver och siffror och filtrera ut skiljetecken som kan vara problematiska, t.ex. liktecknet eller en semikolon.
Använd parametrering och undvik strängkoppling när du skapar SQL-samtal. Lagrade procedurer är inte en panacea eftersom de också kan vara sårbara även om enkel strängkonstruktion används. Parameterisering undviker många av problemen med strängkonstruktion.
Koden som öppnar databasen bör köras med de minsta behörigheterna som behövs för att slutföra de uppgifter som behövs. Under några omständigheter bör databassamtal som används av webbapplikationen behöva göra ändringar i databasstrukturen, såsom att släppa eller ändra tabeller. Du kan lägga till ett extra skyddslag genom att köra separata delar av webbplatsen under olika konton. Databaskontot som används för normala användaråtgärder har sannolikt ingen anledning att ändra tabellen som innehåller rollerna eller användarnas rättigheter. Att köra de administrativa delarna av webbplatsen under ett mer privilegierat konto och slutanvändarsektionen under en mindre privilegierad kan göra mycket för att mildra chanserna för kod som släpper igenom från att orsaka andra problem.