Kā pasargāt sevi no SQL injekcijām

22
May
10

Mūžam vecā lieta, kad kāds aizmirsis pārbaudīt datus, pirms tos liek SQL pieprasījumā, un kāds cits ir atklājis šo ievainojamību. Kā tad maksimāli izvairīties no šādām problēmām?

Ir redzētas dažādas datubāžu pieprasījumu veidošanas klases, bet tā īsti nav sanācis izmantot nevienu. Tad nu lūk, kādas īsti ir priekšrocības mysql_query() iebāzt kādā citā funkcijā? Tādas, ka tā cita funkcija var darīt vēl šo to, piemēram, palīdzēt debug’ot kodu. Precīzāk, saskaitīt nosūtīto pieprasījumu skaitu, izpildes laiku un visu pārējo, ko vēlies redzēt.

Bet ne par to ir runa. Neturēšu sveci zem pūra un parādīšu, manuprāt, nozīmīgāko, kas man neļauj aizmirst, par datu drošību. Kveriju sastādu pēc printf() principa, nevis līmējot stringus. Pieprasījuma veidošana izskatīsies šādi:

$db->query("SELECT id FROM `tabula` WHERE lauks1=%d AND laiks2=%s AND lauks3=%.2f OR lauks4=NOW()", $var1, $var2, $var3);

Tālāko varat iedomāties paši.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function query($query){
	if(func_num_args()>1){
		$args = func_get_args();
		$query = call_user_func_array(array($this, 'make_args'), $args);
	}
	$this->queries[] = $query; // etc
	return mysql_query($query);
}
 
function make_args(){
	$args = func_get_args();
	$arr = array($args[0]);
	foreach($args as $arg){
		$arr[] = $arg==='' ? 'NULL' : ($this->is_numeric($arg) ? $arg : "'".mysql_real_escape_string($arg)."'"); // eskeipojam katru mainīgo, ja tas nepieciešams
	}
	return call_user_func_array('sprintf', $arr);
}
 
function is_numeric($what){
	return (!preg_match('/^\-?\d+(\.\d+)?$/D', $what) || preg_match('/^0\d+$/D', $what)) ? false : true;
}

Arī update funkciju esmu uztaisījis savu, kur padodu tikai masīvu ar atjaunojamajiem laukiem

$db->update('tabula', $id, array(
	'lauks1' => $var1,
	'lauks2' => $var2,
	'~lauks3' => 'NOW()' // SQL'iskas funkcijas nevajag apstrādāt
));

Un konstruējot pieprasījumu, apstrādājam mainīgos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function update($table, (int)$id, $updates, $id_field = 'id'){
	$update = array();
	foreach($updates as $key=>$val){
		list($field, $value) = $this->process_pair($key, $val);
		$update[] = "$field=$value";
	}
	$update = implode(", ", $update);
	return $this->query("UPDATE `$table` SET $update WHERE $id_field=$id");
}
 
function process_pair($field, $value){
	$escape = true;
	if($field[0]=='~'){ // eskeipot mainīgo ?
		$escape = false;
		$field = substr($field, 1);
	}
	return array($field, ($value==='' ? 'NULL' : ($this->is_numeric($value) ? $value : "'".mysql_real_escape_string($value)."'")));
}

Rezultātā praktiski visi mainīgie tiks eskeipoti automātiski un aizmirst par mysql_real_escape_string() lietošanu būs gandrīz neiespējami. Tāpat man patīk arī sintakse, kur nav jālipina kopā stringi.

Jāatzīst, ka diezgan pasen kaut kur jau manīju šādu risinājumu, taču biju piemirsis un pirms pāris mēnešiem atcerējos, nolēmu, ka toreiz manītā ideja man tīri labi patīk un nu jau kādu laiku pats lietoju praksē.

Filed under: PHP, datubāzes
10 Comments

10 Comments

  1. Mārcis
    02:21 on May 22nd, 2009

    Syntax highlight kaut kā galīgi tizli strādā…

  2. ms
    08:05 on May 22nd, 2009

    Līdzīgi tiek darīts arī ZF un CodeIgniter freimworkos, rezēm gan, kad sanāk ģenerēt pagarāku, sarežģītāku vaicājumu, tad šī pieeja var nebūt tā ērtākā.

  3. Mārcis
    11:38 on May 22nd, 2009

    Taisnība, dažreiz ir ērtāk kveriju sametināt un padot jau gatavu, bet šādu gadījumu skaits ir relatīvi mazs, tāpēc neredzu problēmu uztaisīt izņēmumus.
    Ar ZF ir strādāts ļoti, ļoti sen. Atceros, ka toreiz tur bija man vispār nesaprotams pieprasījumu konstruktors.

  4. aaaa
    14:16 on May 28th, 2009

    Tas kaut kā tieši priekš OOP vai kā? Centos palūkot, kas, ko kā, bet vienkāršus php tagus piemetot viņam nepatīk. :) btw, rakstīts, ka emails nav obligāts, bet submitot tāvaitā neļauj, ja nav pievienots.

  5. Mārcis
    01:34 on May 29th, 2009

    AAAA, tas ir izrāvums no klases.
    Visu klasi nepublicēju, jo tas īsti arī nav vajadzīgs, galvenā ideja ir parādīta :)
    P.S. Paldies, ka paziņoji par mail’a bug’u.

  6. k4y
    03:04 on June 3rd, 2009

    Viens jautājums tev, Mārci, vai is_numeric ir pilnībā drošs pasākums pret injekciju, ja $_GET ir skaitliska vērtība?

  7. Mārcis
    03:50 on June 3rd, 2009

    Ja gaidāmais $_GET mainīgais tiek gaidīts kā skaitliska vērtība, tad to jebkurā gadījumā vajadzētu attiecīgi apstrādāt ar intval() vai floatval(), citādi mysql_real_escape_string().
    Ok, papētīsim manas custom ::is_numeric() funkcijas regex – netiek atļauts neviens simbols, kas varētu izraisīt injekcijas pastāvēšanas draudus. Tikai pozitīvi un negatīvi integer/float/double tipa mainīgie tiks laisti cauri bez ekseipošanas.
    Jā, pirmajā gadījumā ::is_numeric funkcija zināmā mērā pat varētu būt lieka, jo sastādot kveriju ar sprintf() principu, tu tāpat norādi gaidāmo datu tipu.

  8. waplet
    20:52 on September 5th, 2009

    Es nesaprotu.. vai tu varēu padalīties ar savu klasi .. to savu OOP un tml?un nelielu pamācībū jo es bišk nesaprotu

  9. Krišjānis
    05:13 on December 31st, 2009

    Izveido primitīvu klasi un ieliec tajā funkcijas..

  10. Mārcis
    18:08 on December 31st, 2009

Leave a comment

RSS barotne komentāriem pie šī raksta