|

Als je in PHP een database gebruikt dan zijn er altijd wel mensen die (My)SQL injections kunnen doen, en dat ook graag willen laten zien. Irongeek heeft daar een goed filmpje van gemaakt, maar in dit artikel leg ik uit hoe je je hiertegen kan verdedigen.
VoorkennisEen (My)SQL injection is een stukje MySQL injecten (dûh) in de PHP code. Een voorbeeldje: mysql_query( "SELECT `userid`, `password` FROM `users` WHERE `username` = '" . $_POST[ 'user' ] . "';" ); Tja, dit is niet zo goed. Stel dat iemand in het user veld nou dit zet: "a' or 'a' = 'a" dan krijg je dit als query: SELECT `userid`, `password` FROM `users` WHERE `username` = 'a' or 'a' = 'a';
Oftewel alle username's worden uit de database gehaald, en dit is duidelijk niet de bedoeling. VerdedigenNou is het niet zo moeilijk om dit tegen te gaan, maar je moet wel consequent zijn, want elke veld dat je vergeet is onveilig. Het beste om dit te doen is de functie mysql_real_escape_string() te gebruiken. Bijvoorbeeld: mysql_query( "SELECT `userid`, `password` FROM `users` WHERE `username` = '" . mysql_real_escape_string( $_POST[ 'user' ] ) . "';" ); Stel dat iemand het weer eens probeert krijg je dit als query: SELECT `userid`, `password` FROM `users` WHERE `username` = 'a\' or \'a\' = \'a'; Tja, en dan werkt het niet meer, want de ' is dan geëscaped. (Als je als resultaat dit krijgt: SELECT [....] ame` = 'a\\' or \\'a\\' = \\'a'; dan gaat het dus fout. Onder het volgende kopje vertel ik er meer over). One-in-all functionWordt je er nou tegend moe van om steeds mysql_real_escape_sting te typen dan heb ik hier de oplossing. Gebruik op de $_GET, $_POST, $_REQUEST en $_COOKIE variabelen de functie sanitize: function sanitize( $input ) { #Thanx php.net: # http://nl3.php.net/manual/en/function.mysql-real-escape- # string.php#78753 if( is_array( $input ) ) { foreach( $input as $k => $i ) { $output[ $k ]=sanitize( $i ); } } else { if( get_magic_quotes_gpc() ) { $input = stripslashes( $input ); } $output = mysql_real_escape_string( $input ); } return $output; }
Eerst kijk ie of de waarde $input een array is, zoja, doe deze funtie op elk element. Als $input geen array is, dan escape em met mysql_real_escape_string(). Het gedeelte dat daar net boven staat lost het probleem van die dubble \ op. Soms is PHP zo geconfigureert om automatisch de $_GET en $_POST te 'sanitizen', als je dat dan nog een keer doet dan hef je het effect op, en dat willen we niet. Met get_magic_quotes_gpc() kijken we of PHP dit al gedaan heeft, zoja, maak het dan weer ongedaan en escape op de goede manier. Ik zorg er altijk voor dat deze regels dus helemaal bovenaan de pagina staan: $get = sanitize( $_GET ); $post = sanitize( $_POST ); Ik zet het gesanitizede in $get. Dit heeft 2 redenen, ten eerste omdat je dan ook nog het origineel kan gebruiken, en het belangrijkst, $get typed toch veel makkelijker dan $_GET eh? Ik pas sanitize niet toe op $_COOKIE omdat ik cookies zowiezo te onveilig vind. In plaats daarvan gebruik ik altijd sessions , ook omdat je in cookies maar een beperkt aantal aan data kan opslaan. Ook gebruik ik het niet bij de $_REQUEST functie, omdat ik die nooit gebruik (omdat dat deels onveilig is want ik vind dat wat via $_GET zou moeten gaan ook via $_GET moet gaan, en niet via $_POST. En andersom natuurlijk ook). Je zou dan ook $_GET en $_POST dubbel sanitizen, en dat kost weer CPU (Ik weet dat het een *kut* smoes is, maar als iemand nou heeeeeeeeeeeel erg lange $_GETs en $_POSTs opstuurt dan duurt het ook wel even). mysql_error()
Ook moet je NOOIT mysql_error() of mysql_errno() info laten zien, zo kan een blackhat ook zien wat er fout ging. n plaats daarvan zou ik zoiets doen: $res = @mysql_query( [....] ); if ( mysql_error() ) { die( "DB error #01" ); }
En dan natuurlijk #01 steeds eentje opvoeren. Zo kun je trouwens ook makkelijk vinden waar het fout ging. sql()Ten slotte heb ik nog een zeer fijne functie, die je scripting waarschijnlijk heel wat tijd en moeite kost: function sql( $query, $retfirst ) { $r = @mysql_query( $query ); if( mysql_errno( ) ) { # De volgende twee regels weg commentarieren als je # klaar bent met debuggen en de site echt online gaat $error = 'MYSQL ERROR #'.mysql_errno().' : <small>' . mysql_error(). '</small><br><VAR>$query</VAR>'; echo $error; return false; } if( strtoupper( substr( $query, 0, 6 ) ) != 'SELECT' ) return array( mysql_affected_rows(), mysql_insert_id() ); $count = @mysql_num_rows( $r ); if( !$count ) return array(); if( $retfirst ) { $f = mysql_fetch_assoc( $r ); mysql_free_result( $r ); $all = array(); $all[] = $f; return $all; } else { $all = array(); for( $i = 0; $i < $count; $i++ ) { $f = mysql_fetch_assoc( $r ); $all[] = $f; } mysql_free_result($r); return $all; } }
Deze functie geeft wel info als de query mislukt, als je klaar bent met debuggen dan moet je deze regels weghalen. Als er een fout is opgetreden dan echoed ie namelijk de query en welke fout is gevonden. |