webForumDet fria alternativet

Lite om sql injections/magic quotes/addslashes..

22 svar · 1 335 visningar · startad av Tulork

TulorkMedlem sedan aug. 2004952 inlägg
#1

Undrar lite över det här med sql injections. Om mitt webhotell inte stödjer magic quotes, finns det något enkelt sätt att skydda sig utan att behöva lägga till massa extra i min redan massiva kod? Tänker då på add slashes och stripslashes?.. Går det att skapa någon form av funktion som automatiskt hanterar alla mina INSERT och UPDATE?

EDIT*

har ju denna funktionen, men vet inte riktigt hur jag ska använda den.

function db_escape ($post){

  if (is_string($post)) {
    if (get_magic_quotes_gpc()) {
       $post = stripslashes($post);
}
     return mysql_real_escape_string($post);
}

   foreach ($post as $key => $val) {
     $post[$key] = db_escape($val);
}
      return $post;
}
colioneMedlem sedan juni 20013 386 inlägg
#2

Du borde kunna göra något likande detta, förutsatt att du vill efterapa magic_quotes.

foreach($_POST as $key => $val){ 
    $_POST[$key] = addslashes($val); 
}

Det bästa vore dock att göra något med mysqli_real_escape_string:
http://se2.php.net/mysqli_real_escape_string

colioneMedlem sedan juni 20013 386 inlägg
#3

Funktionen du postar kan du använda direkt i sql-frågan, alternativt så loopar du igenom variablerna så som jag har gjort och kör dem genom den funktionen istället.

EDIT ->

Självklart bör du gå igenom $_GET också. Även cookie-värden som skickas in i en databas också.

TulorkMedlem sedan aug. 2004952 inlägg
#4

den funktionen ligger i en fil som är inkluderad på alla mina sidor som ska anslujta till databasen. kan du ge ett exemple på hur jag kollar ett par variablar från en sida?

colioneMedlem sedan juni 20013 386 inlägg
#5

Du kan kolla in best-practice exemplet på denna sida:

http://se2.php.net/mysql_real_escape_string

Det du skulle kunna göra är ju att köra loopen om du har några värden i post eller get.

EDIT -> Att gå igenom alla post/get i en foreach-loop som jag har postat är inte det bästa sättet. MEN om man är för lat för att ändra i de sql-frågor man redan har är det en lösning.

TulorkMedlem sedan aug. 2004952 inlägg
#6

kanske värt att ändra var o en för sig... handlar om typ 120st sql-frågor.

colioneMedlem sedan juni 20013 386 inlägg
#7

Ett exempel, skrivet från huvudet är ju att göra nåt sånt här:

if(isset($_POST['submit'])) {
foreach($_POST as $key => $val){ 
    $_POST[$key] = quote_smart($val); 
}
}

function quote_smart($value)
{
    // Stripslashes
    if (get_magic_quotes_gpc()) {
        $value = stripslashes($value);
    }
    // Quote if not integer
    if (!is_numeric($value)) {
        $value = "'" . mysql_real_escape_string($value) . "'";
    }
    return $value;
}
itpastornMedlem sedan feb. 2007280 inlägg
#8

Modern databaskoppling

Tulork skrev:

har ju denna funktionen, men vet inte riktigt hur jag ska använda den.

Det här föråldrade eländet vandrar runt på nätet som en evig plåga. En rekursiv funktion vars enda syfte är att modifiera alla värden i en array.

Personen som skrev denna hade tydligen aldrig hört talas om den oerhört mycket bekvämare array_walk_recursive().

För det andra: Om du är nybörjare och inte vet hur du skyddar dig mot SQL-injektion så finns det bara en enda helsäker metod: prepared statements.

I och för sig bör man aldrig köra en SQL-fråga innan man gjort noggranna indatakontroller. Validera och filtrera varenda fält noga av flera skäl.

Sedan använder du prepared statements. Det innebär att du måste sluta använda det föråldrade, sega och osäkra mysql-gränssnittet och i stället använda mysqli eller PDO.

Inte ens i kombination med mysql_real_escape_string() går man helt säker från SQL-injektion. man kan lura funktionen genom att blanda olika teckenkodningar. Prepared statements ger 100 % säkerhet däremot.

Med PDO får du en kod likt denna för att ansluta till databasen:

try {
    $db = new PDO($dsn,$dbuser,$dbpass);
    // Använder du MySQL?
    // Se http://netevil.org/blog/2006/apr/using-pdo-mysql
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);

    // Ställ gärna in lite standardvärden i MySQL
    // som underlättar för oss svenskar
    // Detta kräver att man har fyllt i tidszonsinfo i MySQL-servern
    // Eftersom vi inte hanterar indata så behövs inget prepared statement
    $ts_sql   = "SET time_zone = 'Europe/Stockholm'";
    $svar     =  $db->query($ts_sql);
    // Lite avancerade inställningar för MySQL, se
    // http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
    // Tolerera INGA fel under utveckling, bli generösare under drift.
    // Mina ändringar från default: STRICT_TRANS_TABLES ->
    //    STRICT_ALL_TABLES, NO_ZERO_DATE
    // NO_AUTO_CREATE_USER och NO_ENGINE_SUBSTITUTION
    // är på som standard, men vanliga
    // PHP-skript borde aldrig utföra operationer där åtgärder
    // de reglerar förekommer
    // För maximal portabilitet, överväg ANSI (ej implementerat här)
    $mode_sql = "SET SESSION sql_mode =
                      'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE'";
     $svar     = $db->query($mode_sql);
}
catch (Exception $e) {
    // Logga felet enligt den metod du valt
    // Visa INTE felet för vanliga användare!
    header('HTTP/1.1 500 Internal Server Error'); 
    // Har du en egen felsida? Visa den i så fall nu.
    exit;
}

Överväg att förvandla detta till en funktion som returnerar PDO-objektet, eller en statisk klassmetod som gör detsamma.

För att använda detta i ett skript:

// Stripslashes är skit! Förlita dig [B]aldrig[/B] på dem!
if ( get_magic_quotes_gpc() && ( ! ini_get('magic_quotes_sybase') ) ) {
    array_walk_recursive($_GET,   'stripslashes');
    array_walk_recursive($_POST,  'stripslashes');
    array_walk_recursive($_COOKIE,'stripslashes');
}

// Ev. indatafiltrering. Kontrollera maxlängd, minimilängd, tag bort
// otillåten HTML, kontrollera att siffror verkligen är siffror, etc

// Här tänker vi oss att en artikels id skickats som GET-variabel
// Och för just detta exempel behövs ingen filtrering

$article_sql = 'SELECT col1, col2, col3, col4 FROM articles
                    WHERE articleID = :articleID';

// Dags att skapa ett prepared statement
// Om du låter PDO kasta exceptions så behövs ev. try-catch
$stmt = $db->prepare($article_sql);
// Jag gillar att få svaren som en ASSOCIATIV array
// http://se2.php.net/manual/en/function.PDOStatement-fetch.php
$result    = $stmt->setFetchMode(PDO::FETCH_ASSOC);

// http://php.net/manual/en/function.PDOStatement-execute.php
$stmt->execute(array('articleID ' => $_GET['article']));

if ( $row = $result->fetch() ) {
    // Förbered  visning av din data
    // Ingen output här tack - detta är dataaccesslogik,
    // inte presentationslogik
} else {
    // Inget hittat
    header('HTTP/1.1 302 Not found'); 
   // Visa hjälpsam felsida
   exit;
}

Låt dig inte avskräckas. Detta är mångdubbelt bättre än mysql-gränssnittet.

Stödjer inte ditt webbhotell PHP 5.1 eller senare (vilket krävs för PDO), så byt!

Lars Gunther

itpastornMedlem sedan feb. 2007280 inlägg
#9

@colione. Kolla array_walk_recursive() ...

FuelMedlem sedan okt. 20001 325 inlägg
#10

jaha .. plötsligt kände jag mig inte så duktig längre :) har nog stått stilla i min php utveckling och bara skrivit enkla funktioner och haft svårt för att börja med klasser. Nu när man hör PDO och ser detta så ser det ut som rena grekiskan. Sparka mig i röven..

FuelMedlem sedan okt. 20001 325 inlägg
#11
Warning: Wrong parameter count for stripslashes()

hmm.. intressant detta..

är detta rätt?

$dsn = "mysql:host=$db_host;dbname=$db_name";
try {
    $db = new PDO($dsn,$db_user,$db_pass);
    $db->setAttribute(PDO::ATTR_PERSISTENT, true);
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
    $ts_sql   = "SET time_zone = 'Europe/Stockholm'";
    $svar     =  $db->query($ts_sql);
    $mode_sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE'";
	$svar     = $db->query($mode_sql);
}
catch (Exception $e) {
    // Testar...
    print "Error!: " . $e->getMessage() . "<br/>";
	die();
    exit;
}

Kör lokalt på WAMP och får: Error!: could not find driver

/edit .. bara php_pdo var ibockat, la till php_pdo_mysql så funkade det.

En while så här?

if ( $row = $result->fetch() ) {
while ($row = $result->fetch() )
{
echo $row['data'];
....
FuelMedlem sedan okt. 20001 325 inlägg
#12

asså.. jag blir inte klok på detta :q

$user_group = "2";

$stmt = $db->prepare("SELECT user_id, user_group, user_name, user_password FROM ".TBL_TRACKED_USERS." WHERE user_group > ?");
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute(array($user_group));
if ($uuu = $stmt->fetchAll()) {
	include "templates/users.php";
} else {
    echo "NEJJJJ!";
}
<table border="1">
    <tr>
      <th>ID</th>
      <th>Username</th>
      <th>Password</th>
    </tr>
<?php
foreach ( $uuu as $u ): echo <<<RAD
    <tr>
      <td>{$u['user_id']}</td>
      <td>{$u['user_name']}</td>
      <td>{$u['user_password']}</td>
    <tr>
RAD;
endforeach;
?>
</table>

verkar finnas så många sätt att dra in/ut data med pdo .. vad gäller ? vad är bäst ?

FuelMedlem sedan okt. 20001 325 inlägg
#13

Måste jag köra global på $db inne i en funktion för att det ska fungera? är det nyttigt?

colioneMedlem sedan juni 20013 386 inlägg
#14

Itpastorn array_walk_recursive med stripslashes() funkar inte om du inte har en egen stripslashes-funktion om skirver över den. Stripslashes förväntar sig nämligen ett argument i taget, array_walk_recursive skickar både nyckeln och värdet i arrayen. Byt mot:

    function stripinput(&$ar) 
    { 
        $ar = stripslashes($ar); 
    } 

    if (ini_get('magic_quotes_gpc')) { 
        array_walk_recursive($_GET, 'stripinput'); 
        array_walk_recursive($_POST, 'stripinput'); 
        array_walk_recursive($_COOKIE, 'stripinput'); 
    }

Angående antal sätt att dra ut och in data i PDO beror det väl på vad man känner för. bindParam är väl kke lite snyggare och du kan begränsa värdena mer exakt. En associativ array tycker jag är ennklare att jobba med om du har många värden som ska in. En prepare med frågetecken tycker jag är lättare att jobba med om man ska bygga upp sql:en automatiskt då kan man nämligen repeata ut antalet frågetecken beroende på arrayen storlek.

Om du ändå annamat OOP-tänket vad gäller PDO, varför inte göra detsamma vad gäller resten av koden? Du kan lika gärna göra en singleton klass i PHP där du returnerar ett objekt, sen instantierar du denna i din config-fil som du alltid inkluderar på sidorna.

Detta är dock ingen bra idé då du missar första värdet:

if ( $row = $result->fetch() ) { 
while ($row = $result->fetch() ) 
{ 
echo $row['data']; 
....
FuelMedlem sedan okt. 20001 325 inlägg
#15

jo.. jag märkte att första värdet försvann :)

men.. nu har jag den här som inkluderas på alla sidor

class db
{
	
/*** Declare instance ***/
private static $instance = NULL;

private function __construct() {
  /*** maybe set the db name here later ***/
}

public static function getInstance()
{

	if (!self::$instance) {
    	$dsn = "mysql:host=localhost;dbname=aaaaa";
    	self::$instance = new PDO($dsn,"aaaaa","xxxxx");;
    	self::$instance-> setAttribute(PDO::ATTR_ERRMODE, true);
    	self::$instance-> setAttribute(PDO::ATTR_PERSISTENT, true);
    	self::$instance-> setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    	self::$instance-> setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
    }
	return self::$instance;
}
	private function __clone(){
}

} /*** end of class ***/

och hämtar så här

	$result = DB::getInstance()->query("SELECT user_id, user_name, user_password FROM ".TBL_TRACKED_USERS." WHERE user_name = '$user_name' AND user_password = '$user_password'");

	if ($result) {
		foreach($result as $row){

som verkar fungera.. hur blandar jag nu in setFetchMode(PDO::FETCH_ASSOC) och EXECUTE array ?

colioneMedlem sedan juni 20013 386 inlägg
#16

Jag brukar iofs se till att lägga instansen i en egen variabel. :)

Om du inte vill blanda in en extra variabel i det hela:

$result = DB::getInstance()->query("SELECT user_id, user_name, user_password FROM ".TBL_TRACKED_USERS." WHERE user_name = '$user_name' AND user_password = '$user_password'")->setFetchMode(PDO::FETCH_ASSOC)

Fast jag tycker det är dumt att köra en query med pdo om du ska ha indata i den. Då är det bättre att preparea.

$db= DB::getInstance();

$stmt = $db->prepare("SELECT user_id, user_name, user_password FROM ".TBL_TRACKED_USERS." WHERE user_name = :user AND user_password = :pass");
// Nu kan du antingen använda bindParam(), eller executa med en array.
//bindParam ser ut såhär:
// $stmt->bindParam(':user', $user_name, PDO::PARAM_STR, 64); där 64 är längden
// $stmt->bindParam(':pass', $user_password, PDO::PARAM_STR, 64);
// $stmt->execute();
// Vill du använda array:
$bindArr = array("user"=>$user_name,"pass"=>$user_password);
$stmt->execute($bindArr);
// $result = $stmt->fetch(PDO::FETCH_ASSOC); <- här kan du speca fetchmode direkt om du inte har gjort det innan. har du gjort det innan så bör du har gjort såhär:
// $stmt->setFetchMode(PDO::FETCH_ASSOC);
// Du kan även fetcha allt på en gång:
// $stmt->fetchAll(PDO::FETCH_ASSOC);
FuelMedlem sedan okt. 20001 325 inlägg
#17

fattar inte vad jag gör för fel

Fatal error: Call to a member function execute() on a non-object in...
colioneMedlem sedan juni 20013 386 inlägg
#18

Och hur ser koden ut?

FuelMedlem sedan okt. 20001 325 inlägg
#19

mja.. hela koden är som ett virrvarr nu när jag försöker byta ut allt .. har tappat orienteringen.

Fatal error: Call to a member function fetchAll() on a non-object
function auth_check($group_access,$level_access)
{

	$now = time();
	$user_timeout = $now-300;

	if (LOG_ONLINE == 1) {
		// Radera äldre än timeout_online
		$query = ("DELETE FROM tracked_online WHERE user_logged < $user_timeout");
		mysql_query($query);
	}
	
	$db = DB::getInstance();
	
	//if ($group_access >= 2) {
	
		if (isset($_SESSION['user_id'])) {
    		
    		$upd_user_sql = "SELECT user_group, user_name, user_pm_unread FROM ".TBL_TRACKED_USERS." WHERE 
    		(user_id = '".$_SESSION['user_id']."') AND 
    		(user_session = '".session_id()."')";
			
    		
    		if (USER_IP_CHECK == 1) {
    			 $upd_user_sql .= " AND (user_ip = '".$_SERVER['REMOTE_ADDR']."')";
			}
			
			$this_user = $db->prepare($upd_user_sql);
			
			//$stmt->execute(array("uid" => $_SESSION['user_id'], "sid" => session_id()));
			
			if ($this_user = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
				
    				if (empty($this_user['user_group']) || ($this_user['user_group'] < $group_access)) {
    					msg_die("No access","You have no access to this page");
    				}
    				define('USER_ID',$this_user['user_id']);
    				define('USER_NAME',$this_user['user_name']);
    				define('USER_PM_UNREAD',$this_user['user_pm_unread']);
    			
    		}

och... mer kommer

FuelMedlem sedan okt. 20001 325 inlägg
#20

asså.. det gamla sättet funkade .. mkt enkelt & utan problem .. har följt 5 olika sätt från olika tutorials .. inget fungerar .. gör si, gör så .. aaaeerrrgggghhllll :x

/edit .. detta verkade fungera

function auth_check($group_access,$level_access)
{

	$now = time();
	$user_timeout = $now-300;
	$db = DB::getInstance();
	
		if (isset($_SESSION['user_id'])) {
    		
    		$upd_user_sql = "SELECT user_group, user_name, user_pm_unread FROM ".TBL_TRACKED_USERS." WHERE 
    		(user_id = :user_id) AND 
    		(user_session = :user_session)";
    		
    		if (USER_IP_CHECK == 1) {
    			 $upd_user_sql .= " AND (user_ip = '".$_SERVER['REMOTE_ADDR']."')";
			}
			
			$stmt = $db->prepare($upd_user_sql);
			$stmt->setFetchMode(PDO::FETCH_ASSOC);
    		$stmt->bindParam(':user_id', $_SESSION['user_id'], PDO::PARAM_INT);
    		$stmt->bindParam(':user_session', session_id(), PDO::PARAM_STR, 30);
			$stmt->execute();
			
			if ( $this_user = $stmt->fetchAll()) {
				print_r($this_user)."<br />";
Array ( [0] => Array ( [user_group] => 3 [user_name] => kenta [user_pm_unread] => 0 ) )
138 ms totalt · 3 externa anrop · cache AV · v20260731051352-full.56cc9887
0 ms — hämta statistik (cache)
0 ms — hämta forumlista (cache)
135 ms — hämta tråd, inlägg och bilagor (db)