Thema Sicherheit: mysql_real_escape_string() & magic_quotes_gpc

Zwischenfrage:
Was ist eigentlich der Unterschied zwischen mysql_real_escape_string() und addslashes(), außer dass die eine Funktion an den MySQL-Server weitergereicht wird während die andere vom PHP-Parser bearbeitet wird? Eigentlich machen die beiden doch das selbe, oder?
Ich könnte mir vorstellen, dass mysql_real_escape_string() einfach "MySQL-spezifischer", also entsprechend der verwendeten MySQL-Version escaped und damit geeigneter weil zukunftssicherer ist...

Ich habe jedenfalls in allen meinen älteren Scripten Formular-Angaben immer nur mit addslashes() escaped. Muss ich die alle umarbeiten? Oh, bitte nicht... :confused:

Martin
 
Laut der Syntax einer Zeichenkettendeklaration in MySQL müssen die ASCII-Zeichen mit der hexadezimalen Darstellung (s. ASCII-Zeichentabelle) 0x00, 0x08, 0x09, 0x0A, 0x0D, 0x1A, 0x22, 0x27 und 0x5C immer durch ein vorangestelltes \-Zeichen maskiert werden. (Die Maskierung der Anfühunrgszeichen 0x22 und 0x27 ist nur je nach eingrenzende Anführungszeichen erforderlich)

Wo die mysql_real_escape_string()-Funktion alle Zeichen bis auf das Backspace-Zeichen (0x08) maskiert, beachtet die addslashes()-Funktion nur die Zeichen 0x00, 0x22, 0x27 und 0x5C.
 
Aha, 1000 Dank.

Also mysql_real_escape_string() maskiert zusätzlich Backspace, Tab, Linefeed, Carriage Return, und SUB.
Diese Zeichen sind doch eigentlich nicht unbedingt kritisch bei Strings die über HTML-Formulare "reinkommen" bzw. lassen sich nicht in Textfelder eingeben, oder? Die CRs und LFs die in Textareas eingegeben werden, wurden auch bisher immer korrekt gespeichert.

Kann ich also meine alten Scripte so lassen? Oder muss ich trotzdem alle Scripte umschreiben um vor Injections sicher zu sein? Was meint ihr?
 
Ich konnte sämtliche Steuerzeichen übermitteln. Dennoch kann ich durch nachfolgenden Test behaupten, dass der praktische Einsatz der addslashes()-Funktion gegenüber der mysql_real_escape_string()-Funktion zumindest auf meinem lokalen System unbedenklich ist. Denn entfernt man den dritten Ausdruck mit dem Alias „c“ gibt es keine Fehlermeldung.
PHP:
<?php

	mysql_connect([…]);
	$string = '';
	for( $i=0; $i<=128; $i++ ) {
		$string .= chr($i);
	}

	$query = "
		SELECT
		        '".mysql_real_escape_string($string)."' AS `a`,
		        '".addslashes($string)."' AS `b`,
		        '".$string."' AS `c`
		";
	$result = mysql_query($query) or die('<p><strong>Fehler bei der Datenbankabfrage:</strong> '.htmlspecialchars(mysql_error()).'</p><pre>'.htmlspecialchars($query).'</pre>');
	var_export(mysql_fetch_array($result, MYSQL_ASSOC));

?>
Dies kann natürlich von System zu System wieder unterschiedlich sein, möglicherweise greift PHP auch noch vor dem Versenden der Anfrage sellbst ein.

Generell würde ich jedoch den Gebrauch der mysql_real_escape_string()-Funktion der addslashes()-Funktion vorziehen. Denn die Funktion wurde ja nicht ganz ohne Bedacht definiert.
 
Gumbo hat gesagt.:
Ich konnte sämtliche Steuerzeichen übermitteln. ...
Wie übermittelst Du denn beispielsweise einen Backspace über ein HTML-Textfeld? Ok, man könnte den Value des Textfeldes per JS setzen, aber sonst... na egal.

Wichtiger ist, dass mich Deine Aussage beruhigt und in meiner Meinung bestätigt. SQL-Injections bauen ja hauptsächlich darauf, dass "Quotes" nicht maskiert werden, um ein bösartiges SQL-Statement dranzuhängen. Und da auch addslashes() alle Quotes brav maskiert...

Gumbo hat gesagt.:
Generell würde ich jedoch den Gebrauch der mysql_real_escape_string()-Funktion der addslashes()-Funktion vorziehen. Denn die Funktion wurde ja nicht ganz ohne Bedacht definiert.
Klar, mache ich ja auch seit einiger Zeit so.

Nochmal Danke,
Martin
 
So, endlich finde ich die Zeit mich weiter um die Sicherheit meiner Skripte zu kümmern.

Als erstes würde ich noch einmal kurz auf die Sicherheit meines Skriptes, mit den Datenbankdaten eingehen. Ich habe zuerst einmal mit .htaccess das Verzeichnis, in welchem diese Datei auf dem Server liegt, vor Zugriffen geschützt.

Dann habe ich Gumbos Code mit dort eingefügt. Ich gehe davon aus, dass es richtig es daran nichts zu verändern? Nun sieht das Skript im Vergleich zu oben wie folgt aus und es funktioniert auch auf dem Server.:

PHP:
 <?php
              
              //Zuerst die Abfrage, ob das Skript aus einem anderen aufgerufen wird
              if( realpath($_SERVER['SCRIPT_FILENAME']) === realpath(__FILE__) ) { 
             		header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found'); 
             		exit; 
             	} 
              
             		//Paramter fuer die Datenbankverbindung
             		$server = "localhost";
             		$user = "root";
             		$passwd = "";
             		$datenbank = "votum";
             		
             		// Versuchen, die DB-Verbindung herzustellen
             		$verbindung = mysql_connect ($server, $user, $passwd);
       if(!$verbindung) die("Datenbankverbindung konnte nicht hergestellt werden. Fehler " . mysql_errno() .": ". mysql_error());
             		
             		//Auswahl der entsprechenden Datenbank
 if (!mysql_select_db ($datenbank)) die("Datenbankverbindung konnte nicht hergestellt werden. Fehler " . mysql_errno() .": ". mysql_error());
             		?>
Dazu habe ich also KEINE Fragen mehr, es sei denn ich hätte irgendwas falsch gemacht? Vielen Dank schon einmal dafür!


Dann ein schönes Beispiel für die Maskierung von Formulareingaben beim Speichern in die Datenbank (SQL-Injektion-Schutz) - und zwar mein Feedbackformular mit dem User einige Angaben machen können, wie Ihnen das Angebot gefällt. Sie enthält sowohl Text (Strings) als auch Zahlenwerte, die aus einem Drop-Down Menu stammen.

Zuerst werden die mittels POST übertragenen Variablen eingelesen:

PHP:
//Variabeln einlesen aus dem Formular
            $beurteilung = trim(@$_POST["beurteilung"]);
            $verbesserung = trim(@$_POST["verbesserung"]);
            $anmerkung = trim(@$_POST["anmerkung"]);
            $wieder = trim(@$_POST["wieder"]);
            $empfehlen = trim(@$_POST["empfehlen"]);
            
            //Variable aus der Session verfuegbar machen
            $besitzerid = $_SESSION["besitzerid"];
Bislang wurden diese Variablen dann einfach ungeprüft in die Datenbank geschrieben, nämlich:
PHP:
	<?php
        
        //Diese Daten werden jetzt in die Datenbanktabelle kontakt geschrieben
          	 
 	 $result = mysql_query("insert into kontakt (besitzerid, beurteilung, verbesserung, anmerkung, wieder, empfehlen) values('$besitzerid', '$beurteilung', '$verbesserung', '$anmerkung', '$wieder', '$empfehlen')");
 	if(!$result) die("Die Kontaktformulardaten konnten nicht gespeichert werden. Fehler " . mysql_errno() .": ". mysql_error());
        	 ?>

Und nun habe ich es folgendermaßen geändert. Dabei ist noch wichtig zu erwähnen, dass ich die magic_quotes abgeschaltet habe!

PHP:
	<?php
      
 $result = mysql_query("INSERT INTO kontakt (besitzerid, beurteilung, verbesserung, anmerkung, wieder, empfehlen) values('".mysql_real_escape_string($besitzerid)."', ".intval($beurteilung).", '".mysql_real_escape_string($verbesserung)."', '".mysql_real_escape_string($anmerkung)."', '".mysql_real_escape_string($wieder)."', '".mysql_real_escape_string($empfehlen)."')"); 
   
       	?>

Aber irgendwas scheint da nicht zu funktionieren. Denn in meiner Datanbank landen die Daten in genau der selben Form wie mit ohne den Maskierungsfunktionen. Außerdem habe ich die Variable $beurteilung mal als string eingegeben und der Wert wird trotz ".intval($beurteilung).", auch als String in der Datenbank gespeichert.

Ist das richtig so ? Oder muss ich wirklich INSERT INTO SET verwenden und es geht nicht mit INSERT INTO VALUES ?

Fehlermeldungen habe ich jedenfalls keine bekommen. Ich hoffe Gumbo oder jemand anderes erbarmt sich erneut und hilft mir auf die Sprünge.
 
Zu deinem ersten Skript möchte ich sagen, dass eine Variablen-Definition für einmalig genutze Werte völlig überflüssig ist. Dementsprechend kannst du die Datenbankverbindungsdaten direkt als Zeichenkette als Parameter übergeben: mysql_connect('localhost', 'root', ''). Übrigens entsprechen die Default-Parameterwerte dieser Funktion genau deinen Datenbankverbindungsdaten, sodass du die Parameter auch gleich weglassen könntest: mysql_connect().

Zu deinem zweiten Skript: Ich hätte die Daten wahrscheinlich so oder ähnlich verarbeitet:
PHP:
<?php

	…

	$_POST['beurteilung'] = ( isset($_POST['beurteilung']) && intval($_POST['beurteilung'])>0 )
		? intval($_POST['beurteilung'])
		: null;
	$_POST['verbesserungen'] = ( isset($_POST['verbesserungen']) && trim($_POST['verbesserungen'])!=='' )
		? trim($_POST['verbesserungen'])
		: null;
	$_POST['anmerkung'] = ( isset($_POST['anmerkung']) && trim($_POST['anmerkung'])!=='' )
		? trim($_POST['anmerkung'])
		: null;
	$_POST['wieder'] = ( isset($_POST['wieder']) && trim($_POST['wieder'])!=='' )
		? trim($_POST['wieder'])
		: null;
	$_POST['empfehlen'] = ( isset($_POST['empfehlen']) && trim($_POST['empfehlen'])!=='' )
		? trim($_POST['empfehlen'])
		: null;

	// mögliche Bedingung gegen Fehleingaben
	// if( !is_null($_POST['beurteilung']) && !is_null($_POST['verbesserung']) && !is_null($_POST['anmerkung']) && !is_null($_POST['wieder']) && !is_null($_POST['empfehlen']) ) {

	$query = "
		INSERT INTO
		        `kontakt`
		  SET
		        `besitzerid`   = '".$_SESSION['besitzerid']."',
		        `beurteilung`  = ".$_POST['beurteilung'].",
		        `verbesserung` = '".mysql_real_escape_string($_POST['verbesserung'])."',
		        `anmerkung`    = '".mysql_real_escape_string($_POST['anmerkung'])."',
		        `wieder`       = '".mysql_real_escape_string($_POST['wieder'])."',
		        `empfehlen`    = '".mysql_real_escape_string($_POST['empfehlen'])."'
		";
	$result = mysql_query($query)
		or die('<p><strong>Fehler bei der Datenbankabfrage:</strong> '.htmlspecialchars(mysql_error()).'</p><pre>'.htmlspecialchars($query).'</pre>');

	// }

	…

?>
Ich persönlich bevorzuge die SELECT-Syntax mit SET-Klausel, die ist mir einfach übersichtlicher. Dennsoch sollte auch die andere Syntax funktionieren.
 
Vielen lieben Dank für die neuen Tips und Hilfen.

Jetzt würde mich nur noch interessieren, wie ich denn überprüfen kann, ob das wirklich funktioniert.

Was müßte ich also in der Eingabe des Formulars als Text z.B. angeben und was sollte dann nach .mysql_real_escape_string in der Datenbank stehen ?

Oder steht in der Datenbank immer das Gleiche und ich kann mir das Ergebnis dieser Funktionen gar nicht anschauen?

Über ein Beispiel würde ich mich freuen.
 
Ok, wenn ich das nun richtig verstanden habe so kann ich davon ausgehen, dass ich es richtig gemacht habe, solange keine Fehlermeldungen erscheinen beim Versuch die Daten in die Datenbank zu transferieren.

Grazie
Euer Don
 
Zurück