Welcome, Guest. Please login or register.
Did you miss your activation email?
May 26, 2012, 10:49:04 AM

Login with username, password and session length
Search:     Advanced search
Interested in joining the WebsiteBaker team?
For more Information read here or on our new website.
155538 Posts in 21715 Topics by 7737 Members
Latest Member: chris85
* Home Help Search Login Register
Pages: [1]   Go Down
Print
Author Topic: Module: Klassen, Variablen und Sicherheit  (Read 2857 times)
thorn

Offline Offline

Posts: 980


WWW
« on: March 28, 2010, 04:34:27 PM »

Hallo,
Diskussion bitte hier: http://www.websitebaker2.org/forum/index.php/topic,17748.0.html

Changelog:
25 August 2010 - Fehler bei Angaben zu print_error() und print_success() behoben.
20 Juli 2010 - /modules/admin.php: Hinweis hinzu bei "Prüft ob der Benutzer die aktuelle Seite bearbeiten darf". Es wird anhand User_ID/Gruppen-Zugehörigkeit geprüft.
21 April 2010 - fehlende require_once(WB_PATH.'/framework/class.admin.php'); bei Beispielen hinzu.
07 April 2010 - Tippfehler in Beispiel zu $admin->get_permission() korrigiert.
05 April 2010 - bei require(WB_PATH.'/modules/admin.php'); Bemerkung entfernt, und einzelne Punkte weiter eingerückt (unter Instanz der Klasse admin erzeugen).
02 April 2010 - fertiggestellt


require('../../config.php');
Definiert die notwendigen Konstanten (WB_PATH, ...), und lädt framework/initialize.php.
Damit wird WebsiteBaker quasi "gestartet".
Wird im Backend und Frontend benötigt für Dateien die nicht von wb geladen werden, die also direkt vom Browser oder per Weiterleitung geladen werden.


if(!defined('WB_PATH')) die('Cannot access this file directly');
Bricht ab, wenn die Konstante "WB_PATH" nicht definiert ist.
Verhindert daß die Datei direkt über einen Browser (oder sonstwie) ausgeführt wird.
Soll in jeder Datei die von wb oder per include/require geladen wird ganz am Anfang stehen.


require(WB_PATH.'/modules/admin.php');
Lädt die Datei modules/admin.php. Dieser Aufruf sollte in jedem Page-Modul benutzt werden, jedoch nicht in den Standard-Dateien (view.php, modify.php, install.php, uninstall.php, add.php, delete.php).
Admin-Tools und Snippets arbeiten anders. Dort wird dieser Aufruf nicht benötigt.
Diese Datei macht folgendes:
  • erzeugt $page_id aus $_GET oder $_POST. Weiterleitung nach index.php, wenn nicht vorhanden.
  • erzeugt $section_id aus $_GET oder $_POST. Weiterleitung nach index.php, wenn nicht vorhanden (optional, wenn $section_required gesetzt ist).
  • erzeugt $js_back als 'javascript: history.go(-1);'
  • erzeugt eine neue Instanz der Klasse admin:
    • gibt das Admin-Menu aus (Seiten | Medien | Erweiterungen | ...).
    • Prüft ob der Benutzer angemeldet ist. Weiterleitung zur Login-Seite wenn nicht.
    • Prüft ob der Benutzer die Berechtigung "pages_modify" hat. Weiterleitung nach index.php wenn nicht.
  • Prüft ob der Benutzer die aktuelle Seite bearbeiten darf (bezogen auf admin_groups/admin_users). Weiterleitung nach index.php wenn nicht.
  • anzeigen des Templates pages_modify.htt (optional, wenn $print_info_banner gesetzt ist).
  • Änderungsdatum der Seite anpassen (optional, wenn $update_when_modified gesetzt ist)
Muß im Backend bei jeder Datei benutzt werden die nicht von wb/php geladen werden, die also direkt vom Browser oder per Weiterleitung geladen werden, und die etwas anzeigen sollen.
Dateien im Backend, die nichts anzeigen sollen (z.B. moveup.php) müssen anders aufgebaut sein.
Normalerweise wird dieser Aufruf nur bei Modulen vom Typ "Page" benutzt (WYSIWYG, NEWS, ...). AdminTools und Snippets arbeiten anders, und benötigen diesen Aufruf nicht.

Zu beachten ist, das dieser Aufruf das Admin-Menu mit ausgibt. Ein Aufruf von $admin->print_footer() am Ende der Datei ist daher zwingend notwendig.
Der normale Aufbau einer Datei die diesen Aufruf benutzt sieht so aus:

Code: (Z.B.: modify_settings.php)
<?php
require('../../config.php');

$section_required TRUE;
require(
WB_PATH.'/modules/admin.php');

// Zeige Formular an

// Print admin footer
$admin->print_footer();
?>


Dateien wie save_settings.php, die von einem Formular aus aufgerufen werden, müssen so aufgebaut sein:
Code: (Z.B.: save_settings.php)
<?php
require('../../config.php');

$section_required TRUE;
require(
WB_PATH.'/modules/admin.php');

// Lese POST-Daten des Formulars

// Update settings
$database->query("UPDATE ".TABLE_PREFIX."mod_xxx_settings SET ... WHERE section_id = '$section_id'");

// Check if there is a db error, otherwise say successful
if($database->is_error()) {
$admin->print_error($database->get_error(), ADMIN_URL.'/pages/modify.php?page_id='.$page_id);
} else {
$admin->print_success($TEXT['SUCCESS'], ADMIN_URL.'/pages/modify.php?page_id='.$page_id);
}

// Print admin footer
$admin->print_footer();
?>



print_error() und print_success()
Der Aufruf von print_error() bricht die Ausführung des Skriptes ab, und zeigt eine Fehlermeldung an. Das ursprüngliche Skript wird nicht weiter ausgeführt.
Der Aufruf von print_success() beinhaltet eine Weiterleitung, die automatisch nach einer kurzen Wartezeit ausgelöst wird.


require_once(WB_PATH.'/framework/class.admin.php');
$admin = new admin(...);

Erzeugt eine Instanz der Klasse admin und macht folgendes:
  • prüft ob der Benutzer angemeldet ist. Weiterleitung zur Login-Seite wenn nicht. Wenn der Benutzer angemeldet ist: prüft ob der Benutzer die angegebene Berechtigung hat. Abbruch wenn nicht. (optional)
  • prüft Backend-Sprache. Ggf. Umleitung (http://...&Lang=XX)
  • anzeigen des Admin-Menüs (optional).
Dieser Aufruf wird im Backend nur dann benötigt, wenn require(WB_PATH.'/modules/admin.php'); nicht eingesetzt werden kann. Also für Dateien, die keinerlei Ausgabe erzeugen sollen.

Zur weiteren Erklärung muß man wohl etwas ausholen:
Prototype: function admin($section_name, $section_permission = 'start', $auto_header = true, $auto_auth = true)
$section_name - ein String. Erlaubte Werte sind "pages", "media", "addons", "preferences", "settings", "admintools", "access".
Wird lediglich benutzt um im Admin-Menu den aktuellen Eintrag zu setzen (setzt class="current").
$section_permission - ein String. Erlaubte Werte sind:
pages, pages_view, pages_add, pages_add_l0, pages_settings, pages_modify, pages_intro, pages_delete,
media, media_view, media_upload, media_rename, media_delete, media_create,
addons,
modules, modules_view, modules_install, modules_uninstall,
templates, templates_view, templates_install, templates_uninstall,
languages, languages_view, languages_install, languages_uninstall,
settings, settings_basic, settings_advanced,
access,
users, users_view, users_add, users_modify, users_delete,
groups, groups_view, groups_add, groups_modify, groups_delete,
admintools,
start

Die genaue Bedeutung der einzelnen Strings und die Benutzung muß an anderer Stelle erklärt werden.
Dies ist die Berechtigung auf die geprüft werden soll.
$auto_header bool. Erlaubte Werte: TRUE, FALSE.
Legt fest, ob das Admin-Menu angezeigt werden soll (TRUE), oder nicht (FALSE).
Wenn dieser Wert TRUE ist, muß am Ende der Datei $admin->print_footer() aufgerufen werden.
$auto_auth bool. Erlaubte Werte: TRUE, FALSE.
Legt fest, ob der Benutzter angemeldet sein muß (TRUE), oder nicht (FALSE).
Wenn dieser Wert TRUE ist wird ein nicht angemeldeter Benutzer auf die Login-Seite umgeleitet.
ACHTUNG: Wenn $auto_auth auf FALSE steht wird die Berechtigung nicht geprüft.

Eine Datei, im Backend aufgerufen, die keine Ausgabe erzeugen soll, muß so aufgebaut werden:
Code:
<?php
require('../../config.php');

require_once(
WB_PATH.'/framework/class.admin.php');
$admin = new admin('Pages''pages_modify'FALSETRUE);

// code hier

die(header('Location: '.ADMIN_URL.'/pages/modify.php?page_id='.$page_id));
?>




$admin->get_permission(...)
Prüft, ob der aktuell angemeldete User eine bestimmte Berechtigung (System, Modul oder Template) besitzt.
Prototype: function get_permission($name, $type = 'system')
$name - ein String. Name der zu prüfenden Berechtigung. Bei Modul-Berechtigung ist dies der Name des Moduls, bei Template-Berechtigung der Name des Templates.
$type - ein String. Gültige Werte sind 'system', 'module' oder 'template'.
Code:
if(!$admin->get_permission('pages_modify', 'system')) {
  // Benutzer hat die Berechtigung 'pages_modify'
}
Code:
if(!$admin->get_permission('admintools', 'system')) {
  // Benutzer hat die Berechtigung 'admintools'
}
Code:
if(!$admin->get_permission('wysiwyg', 'module')) {
  // Benutzer darf das WYSIWYG-Modul benutzen
}



Wie sieht das nun in der Praxis aus?
Fangen wir mit dem einfachsten Fall an. Wie soll der "Kopfbereich" der Dateien aussehen?

Snippets
Snippets werden ausschließlich im Frontend ausgeführt. Dazu werden sie per include() geladen.
Sie sollen nur aus einer einzelnen Datei (include.php) bestehen, in der sich im Normalfall nur Funktions-Deklarationen befinden. Eine einfache Absicherung gegen direktes Aufrufen reicht also aus:
Code: (include.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// snippet code
...
?>


Admin-Tools
Admin-Tools werden ausschließlich im Backend ausgeführt. Dazu werden sie über einen "Wrapper" (admin/admintools/tool.php) geladen.
Ein Admin-Tool sollte im Idealfall nur aus einer einzelnen Datei (tool.php) bestehen.
Eine einfache Absicherung gegen direktes Aufrufen reicht im Normalfall aus (da der Wrapper den Rest erledigt).
Code: (tool.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// admin-tool code
...
?>

Werden Dateien per include() oder require() nachgeladen, reicht der einfache Schutz gegen direktes Aufrufen.
Code: (Datei per include())
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code hier
...
?>

Für den ganz seltenen Fall, daß eine Datei direkt über den Browser oder per Umleitung aufgerufen wird ist das folgende Vorgehen notwendig:
Code:
<?php
require('../../config.php');

require_once(
WB_PATH.'/framework/class.admin.php');
$admin = new admin('Admintools''admintools'FALSETRUE);

// code hier

?>



normale Module (page-Type)
Diese Module bestehen aus einer ganzen Reihe von Dateien. Einige davon werden durch WB selber aufgerufen, andere werden dagegen direkt durch den Browser oder durch Umleitung aufgerufen; einige im Backend, einige im Frontend.

Für die durch WB im Backend aufgerufenen Dateien
  • add.php
  • delete.php
  • modify.php
reicht im Grunde wieder der Schutz vor direktem Aufrufen
Code: (add.php, delete.php, modify.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>


Für die Backend-Dateien, die nicht von WB aufgerufen werden, typischerweise
  • save.php
  • modify_settings.php
  • save_settings.php
  • ...
soll normalerweise require(WB_PATH.'/modules/admin.php'); benutzt werden:
Code:
<?php
require('../../config.php');

// Include WB admin wrapper script
$section_required TRUE// requires section_id
$update_when_modified TRUE// Tells script to update when this page was last updated
require(WB_PATH.'/modules/admin.php');

// code

// Check if there is a database error, otherwise say successful
if($database->is_error()) {
$admin->print_error($database->get_error(), $js_back);
} else {
$admin->print_success($MESSAGE['PAGES']['SAVED'], ADMIN_URL.'/pages/modify.php?page_id='.$page_id);
}

// Print admin footer
$admin->print_footer();
?>


Für die Dateien die im Backend per include() geladen werden reicht der Schutz vor direktem Aufruf.
Code: (per include())
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>



Für die durch WB im Frontend aufgerufene Datei
  • view.php
reicht der Schutz vor direktem Aufrufen
Code: (view.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>


Für die Frontend-Dateien, die nicht von WB aufgerufen werden, typischerweise sowas wie
  • add_comment.php
  • save_comment.php
  • ...
wird nur dies benutzt:
Code:
<?php
require('../../config.php');

// code hier

?>

Achtung: gerade hier müssen andere Methoden gefunden werden, um ein unkontrolliertes Benutzen (z.B. Spam-Kommentare) dieser Dateien zu verhindern, z.B. der Einsatz von CAPTCHAS oder ASP.

Für die Dateien die im Frontend per include() geladen werden reicht der Schutz vor direktem Aufruf.
Code: (per include())
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>



thorn.
« Last Edit: November 24, 2010, 02:31:55 PM by thorn » Logged

thorn

Offline Offline

Posts: 980


WWW
« Reply #1 on: April 01, 2010, 07:50:54 PM »

Changelog:
02 April 2010 - fertiggestellt


Hallo,

nächster Teil: Gruppen-Zugehörigkeit im Frontend feststellen. Und Home-Folder ermitteln (weil es sonst nirgens hinpaßt).

$wb ist eine Instanz der Klasse wb, die im Frontend automatisch zur Verfügung steht (zumindest in den Dateien, die durch WB aufgerufen werden, und den von solchen Dateien per include/require nachgeladenen).

In Dateien, die durch den Browser aufgerufen werden (per angeklickten Link oder Weiterleitung) kann die Instanz so erzeugt werden:
Code:
<?php
require_once(WB_PATH.'/framework/class.wb.php');
$wb = new wb;



$wb->is_authenticated()
Prüft, ob der aktuelle Benutzer angemeldet (registriert) ist. Liefert TRUE oder FALSE.
Code:
<?php
if($wb->is_authenticated()) {
  
// Benutzer ist angemeldet
}

Wenn der aktuelle Benutzer angemeldet ist, können die folgenden Methoden benutzt werden.
Achtung: ist der aktuelle Benutzer nicht angemeldet, führt die Benutzer dieser Methoden in der Regel zu einem Fehler (zumindest E_NOTICE).


$wb->get_groups_id()
Liefert Array mit den Gruppen-Ids, denen der aktuelle Benutzer zugeordnet ist, z.B.: array('3','5','15','4').

$wb->get_groups_name()
Liefert Array mit den Gruppen-Namen, denen der aktuelle Benutzer zugeordnet ist, z.B.: array('Admins','Leser','Editoren','Tester').
Code:
<?php
if($wb->is_authenticated()) {
  
$groups $wb->get_groups_name();
  if(
in_array('Editoren'$groups)) {
    
// erzeuge "Frontend-Editor"-Link
  
}
  if(
in_array('Tester'$groups)) {
    
// erzeuge "Melde Fehler"-Link
  
}
  if(
in_array('Leser'$groups)) {
    
// erzeuge "Seite Bewerten"-Link
  
}
else {
  
// erzeuge "Seite anonym Bewerten"-Link
}

$wb->get_home_folder()
Liefert das Home-Verzeichnis des aktuellen Benutzers, relativ zu MEDIA_DIRECTORY, z.B.: "/hans".

Das war es an dieser Stelle tatsächlich schon ...

EDIT: die Funktion get_group_id() ist veraltet und darf nicht mehr benutzt werden!. Stattdessen get_groups_id() benutzen.


thorn.
« Last Edit: April 05, 2010, 08:48:16 PM by thorn » Logged

thorn

Offline Offline

Posts: 980


WWW
« Reply #2 on: April 02, 2010, 07:08:40 PM »

Changelog:
13 März 2011 - Bei HTML-Ausgabe von GET oder POST-Variablen wird _immer_ htmlspecialchars() benötigt!
21 September 2010 - noch ein $wb->add_slashes() in $wb->strip_slashes() geändert.
25 August 2010 - viele $wb->add_slashes() zu  $wb->strip_slashes() geändert -- macht natürlich mehr Sinn.
19 April 2010 - Beispiel zu strip-tags() erweitert
14 April 2010 - $wb->add_slashes() zu den meisten Beispielen hinzu.
07 April 2010 - vergessenen ENT_QUOTES bei Beispiel zu htmlspecialchars() eingefügt.
05 April 2010 - Kommentar in Beispiel preg_quote erweitert.
05 April 2010 - fertiggestellt

Hallo,

dritter Teil: Behandlung von GET/POST-Daten.

Die Benutzung von GET- oder POST-Daten völlig ohne Sicherungsmaßnahmen ist das Haupt-Einfallstor für eine ganze Reihe von Gefahren, allen voran XSS und SQL-Injection. Darum ist bei der Arbeit mit Werten aus dem GET- oder POST-Array immer Vorsicht notwendig.

Einschub: $_REQUEST sollte auf keinen Fall benutzt werden, da bei Benutzung von $_REQUEST - je nach Server-Konfiguration - die Gefahr besteht, daß ein Angreifer einen Wert aus $_POST durch einem untergeschobenen Wert aus $_GET "überschreibt", oder anders herum.
 
Beispiele für Gefahren, die sich aus der "unkontrollierten" Benutzung von $_GET oder $_POST ergeben:
Code:
$id = $_GET['user_id'];
$query_user = $database->query("SELECT permissions FROM table WHERE user_id='$id'");
Wenn auf dem Server magic_quotes_gpc deaktiviert ist entsteht mit
Code:
http://...php?user_id=-10' OR name='Admin'; --
dies
Code:
SELECT permissions FROM table WHERE user_id='-10' OR name='Admin'; --'
Mit aktiviertem magic_quotes_gpc funktioniert dieser Hack nicht mehr
Code:
SELECT permissions FROM table WHERE user_id='-10\' OR name=\'Admin\'; --'
(Einschub: Dieses Beispiel ist natürlich blöd! Wer sowas im Frontend benutzt dem gehört der Computer entzogen!)

Aber selbst magic_quotes_gpc schützt nicht vor XSS-Angriffen:
Code:
$class = $_GET['class'];
echo "<p class=\"".$class."\">".$text."</p>";
Mit aktiviertem magic_quotes_gpc
Code:
http://....php?class="><script>alert(String.fromCharCode(39)%2BString.fromCharCode(33)%2BString.fromCharCode(39));</script>
ensteht
Code:
<p class="\"><script>alert(String.fromCharCode(39)+String.fromCharCode(33)+String.fromCharCode(39));</script>">TEXT</p>


Funktionen für die Benutzung mit mySQL-Queries

Die folgenden Methoden stehen sowohl in der Klasse wb als auch in der Klasse admin bereit.
Im Backend werden sie über $admin-> angesprochen, im Frontend über $wb->

$wb->add_slashes($string) - Maskiert bestimmte Zeichen aus $string, d.h. stellt ihnen ein \ voran, wenn auf dem Server magic_quotes_gpc deaktiviert ist. Wenn magic_quotes_gpc aktiviert ist erledigt PHP dies selber.
Die folgenden Zeichen werden maskiert: ' " \ \x00 (Null-Byte)
Diese Funktion dient dazu, für alle Strings aus $_GET oder $_POST, unabhängig von der Server-Konfiguration, immer korrekt maskierten Strings zu erhalten. Wie oben gesehen, ist diese Maskierung nur für Datenbank-Operationen wichtig. Für HTML-Ausgabe siehe htmlspecialchars()/strip_slashes().
Eingabe: http://...php?name=o'Reilly
Code:
<?php
$name 
$_GET['name']; // möglicherweise "o'Reilly" oder "o\'Reilly"
$name addslashes($_GET['name']); // FEHLER: liefert "o\'Reilly" oder "o\\\'Reilly"
$name $wb->add_slashes($_GET['name']); // RICHTIG: auf jeden Fall "o\'Reilly"

$wb->strip_slashes($string) - Entfernt (führende) \ aus $string, wenn auf dem Server magic_quotes_gpc aktiviert ist.
Diese Funktion dient dazu, für alle Strings aus $_GET oder $_POST, unabhängig von der Server-Konfiguration, immer den Originalstring zu erhalten.
Eingabe: http://...php?name=o'Reilly \text
Code:
<?php
$name 
$_GET['name']; // möglicherweise "o'Reilly \text" oder "o\'Reilly \\text"
$name stripslashes($_GET['name']); // FEHLER: liefert "o'Reilly text" oder "o'Reilly \text"
$name $wb->stip-slashes($_GET['name']); // RICHTIG: auf jeden Fall "o'Reilly \text"


$wb->get_post_escaped($field) - enspricht $wb->add_slashes($_POST[$field])


Weitere wichtige Funktionen von PHP:

addslashes($string) - Maskiert $string.
addslashes() wird gerne benutzt, um Werte die in einer Datenbank-Abfrage benutzt werden sollen zu maskieren:
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']); // Original-String herstellen
echo "Der Name ist: ".htmlspecialchars($nameENT_QUOTES);
$name addslashes($name); // maskieren
$query $database->query("SELECT * FROM table WHERE name='$name'");
Allerdings ist die Benutzung von addslashes() zu diesem Zweck seit bekanntwerden eines Exploits in Verruf gekommen. Zwar muß, damit dieser Exploit funktioniert, mySQLs default-charset auf GBK aka simplified Chinese stehen, aber nichtsdestotrotz sollte man auf mysql_real_escape_s tring() umsteigen.

mysql_real_escape_s tring($string) - maskiert bestimmte Zeichen in $string für die Verwendung in einem mySQL-Query. Hierfür muß eine geöffnete Verbindung zu mySQL bestehen.
Diese Funktion wird als sicherer als addslashes() angesehen, da sie zum einen mehr Zeichen maskiert (\x00, \n, \r, \, ', " und \x1a), und sie zum anderen nicht wie addslashes() anfällig für Multi-Byte-Character Exploits ist.
Die Funktion wird genauso wie addslashes() benutzt:
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']); // Original-String herstellen
echo "Der Name ist: ".htmlspecialchars($nameENT_QUOTES);
$name mysql_real_escape_string($name); // maskieren
$query $database->query("SELECT * FROM table WHERE name='$name'");

is_numeric($string), intval($string), casting,  ...
Eine ganze Reihe von Funktionen, mit deren Hilfe man Werte auf ihren Daten-Typ hin untersuchen, oder sie in einen bestimmten Daten-Typ umwandeln kann.
Code:
<?php
// $user_id must be integer:
if(is_numeric($_GET['user_id'])) {
  
$user_id $_GET['user_id'];
} else {
  
$user_id 0;
}
$query $database->query("SELECT * FROM table WHERE id=$user_id");
Kürzer geht es so:
Code:
<?php
// $user_id must be integer:
$user_id intval($_GET['user_id']);
$query $database->query("SELECT * FROM table WHERE id=$user_id");
Funktion              Resultat
is_numeric("10")TRUE
is_numeric(" 10 ")FALSE
is_numeric("10 text")FALSE
is_numeric("text 10")FALSE
is_numeric("")FALSE
intval("10")10
intval(" 10 ")10
intval("10 text")10
intval("text 10")0
intval("")0
Hier erkennt man auch warum man niemals den Wert 0 für IDs in der Datenbank benutzen darf.

addcslashes($string, $charlist) - Maskiert alle Zeichen aus $charlist in $string.
Eine kaum benutzte Funktion, die jedoch für eine bestimmte Sache sehr nützlich ist, und zwar für SQL-Queries, die LIKE benutzen.

Das folgende Query soll eine Liste alle Personen erzeugen, deren Name mit $name beginnt
Code:
<?php
$name 
mysql_real_escape_string($wb->strip_slashes($_GET['name']));
$query $database->query("SELECT * FROM table WHERE name LIKE '".$name."%');
Wenn auf dem Feld name ein Index liegt, ist dieses Query ausreichend performant.
Ein Angreifer könnte nun einen DOS-Angriff (Denial of Service) starten, indem er eine große Zahl an Suchanfragen in der Form http://...php?name=%_a an den Server schickt. Denn dadurch entsteht
Code:
SELECT * FROM table WHERE name LIKE '%_a%'
(Suche alle Namen, die beliebig beginnen, mit einem beliebigen Zeichen weitergehen, gefolgt von einem a, und dann beliebig enden), was in einer furchtbaren Table-Scan-Orgie endet.
Gegenmaßnahme:
Code:
<?php
$name 
mysql_real_escape_string($wb->strip_slashes($_GET['name']));
$name addcslashes($name'_%'); // maskiere % und _
$query $database->query("SELECT * FROM table WHERE name LIKE '".$name."%');
Nun entsteht
Code:
SELECT * FROM table WHERE name LIKE '\%\_a%'
(Suche alle Namen die mit % beginnen, gefolgt von einem _, gefolgt von a, und die beliebig enden), und der Angriff ist abgewehrt.



Funktionen für die Benutzung mit HTML-Ausgaben

htmlspecialchars($string, ENT_QUOTES) - Wandelt alle & < > ' " in HTML-Entities (&amp;, &lt;, ...) um.
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']);
$name htmlspecialchars($nameENT_QUOTES);
echo 
"Name: <strong>$name</strong>";
Eingabe: Hans<script>alert('Hallo!');</script>
Ausgabe: Hans&lt;script&gt;alert(&#039;Hallo!&#039;);&lt;/script&gt;
Nicht schön, aber sicher.

strip_tags($string, $allowed_tags) - entfernt alle HTML-Tags aus $string. Die in $allowed_tags angegebenen Tags werden nicht entfernt.
Achtung: Die alleinige Nutzung von strip_tags() reicht nicht aus, da dabei " und ' erhalten bleiben, wodurch XSS-Angriffe möglich werden!
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']);
$name htmlspecialchars(strip_tags($name), ENT_QUOTES);
echo 
"Name: <strong>$name</strong>";
Eingabe: Hans<script>alert('Hallo!');</script>
Ausgabe: Hansalert(&#039;Hallo!&#039;);
Auch nicht schön, aber wiederum sicher.

Im nächsten Beispiel sieht man, warum htmlspecialchars() immer notwendig ist.
Außerdem zeigt es, warum von der Benutzung von $allowed_tags eher abzuraten ist:
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']);
$name strip_tags($name"<b><strong><i>");
echo 
"Name: <strong>$name</strong>";
Eingabe: Hans <b onmouseover="alert('Hello!')">Hänschen</b> Mustermann<script>alert('Hallo!');</script>
Ausgabe: Hans <b onmouseover="alert('Hello!')">Hänschen</b> Mustermannalert('Hallo!');

Insgesamt kann man sagen, daß man auf strip_tags() eher verzichten sollte, da es z.B. auch einfach alles zwischen < und dem nächsten > (oder bis zum Stringende) entfernt:
Code:
<?php
echo strip_tags($wb->strip_slashes($_GET['name']);
Eingabe: Außerdem gilt immer noch 1<2. Wäre sonst ja auch schlimm!
Ausgabe: Außerdem gilt immer noch 1



Funktionen für die Benutzung in Links

urlencode($string) - Kodiert $string zur Benutzung in einer URL.
Bestimmte Zeichen (z.B. : / ? &) müssen in einer URL kodiert werden, damit sie nicht als "Steuerzeichen" interpretiert werden. Dazu dient diese Funktion.
Beispiel: Der String "R&B" soll als Suchbegriff per URL übergeben werden:
Code:
http://www.example.org/wb/search/index.php?search=R&B
funktioniert nicht, da das & als "Steuerzeichen" interpretiert wird (& kennzeichnet den Anfang eines neuen Wertes). Statt search="R&B" erhält man search="R" und B="".
Richtig geht es so:
Code:
<?php
$search 
urlencode("R&B"); // erzeugt "R%26B"
echo "http://www.example.org/wb/search/index.php?search=".$search;
Ausgabe:
Code:
http://www.example.org/wb/search/index.php?search=R%26B
Beachten Sie, daß in der Zielseite dieser Wert automatisch dekodiert wird! Ein Aufruf von urldecode() ist nicht notwendig.
Code:
<?php
$search 
$wb->strip_slashes($_GET('search'));
echo 
$search// Ausgabe: R&B
Will man mehrere Werte übergeben, trennt man die einzelnen Werte durch &amp;. & alleine würde auch reichen, allerdings besteht die Gefahr, daß dadurch HTML-Entities entstehen:
Code:
<?php
echo '<a href="http://www.example.org/pages/page.php?row=4&copy=1">link</a>';
Erzeugt:
Code:
http://www.example.org/page/page.php?row=4©=1
Besteht ein Wert mit Sicherheit nur aus einer Zahl kann auf urldecode() verzichtet werden.

Komplettes Beispiel:
Code:
<?php
$link 
WB_URL.'/modules/mod/file.php';
$url_query '?';
$url_query .= 'page_id='.$page_id;
$url_query .= '&amp;';
$url_query .= 'name='.urlencode($name);
echo 
'<a href="'.$link.$url_query.'">link</a>';
oder kompakter
Code:
<?php
echo '<a href="'.WB_URL.'/modules/mod/file.php?page_id='.$page_id.'&amp;name='.urlencode($name).'">link</a>';



Sonstige Funktionen

preg_quote($string, $begrenzer) - maskiert Spezielle-Zeichen in $string zur Verwendung in einem Regulären-Ausdruck. $begrenzer ist das als Begrenzer benutzte Zeichen, das ebenfalls maskiert wird.
Für den Fall, daß eine Benutzer-Eingabe, oder allgemein ein unbekannter String, in einem Regulären-Ausdruck benutzt werden soll, müßen bestimmte Zeichen (z.B. * und ?) maskiert werden.
Code:
<?php
$str 
$wb->strip_slashes($_POST['suchwort']);
$str preg_quote($str'~'); // maskiert alle speziellen Zeichen, und ~, das in der nächsten Zeile als Begrenzer benutzt wird
if(preg_match('~\b'.$str.'~i'$text)) {
  
// Treffer
}
Anderenfalls kann ein Fehler auftreten (z.B. suchwort="V8("), oder es könnte sogar für einen DOS-Angriff genutzt werden.



Siehe auch: Filter-Erweiterung die seit PHP 5.2 benutzt werden kann.

thorn.
« Last Edit: March 13, 2011, 03:26:29 PM by thorn » Logged

Waldschwein
Guest
« Reply #3 on: April 14, 2010, 11:47:43 AM »

Hallo!

Ich habe einmal eine Frage: Das WebsiteBaker "framework" stellt ja eine Klasse class.order.php zur Verfügung.
Jetzt benutzt aber jedes Modul seine eigene Sortierung - auch Coremodule wie JSadmin.
Wie kann man eigentlich mit einem Modul die "class order" ansprechen ("class moduleorder extends order"?) und geht das überhaupt? Es würde doch einiges an SQL-Abfragen ersparen, wenn es denn WB ja eh schon bietet. Dass ich natürlich die Datenbank des Moduls dann speziell im Modul eingeben muss in die extended-Class ist natürlich klar. Bietet die Klasse überhaupt diese Möglichkeit?

Gruß Michael
Logged
thorn

Offline Offline

Posts: 980


WWW
« Reply #4 on: April 14, 2010, 01:15:05 PM »

Hallo,

Changelog:
21 April 2010 - fehlende require_once(WB_PATH.'/framework/class.order.php'); zu Beispielen hinzu.
14 April 2010 - fertiggestelt

Quote
class.order.php
Gehört zwar jetzt nicht zur Sicherheit, aber gucken wir einfach mal was die Klasse kann:

Generell dient die Klasse dazu, "Listen", wie z.B. den Seitenbaum zu sortieren. Ein wichtiges Merkmal dieser "Listen" ist, daß die Sortierung in jedem "Ast" (aka. Unterseiten) getrennt passiert.

page_id - parent - pos
101
202
303
421
522
604
711
812
Seitenbaum (page_id -- (pos)):
  • 1 -- (1)
    • 7 -- (1)
    • 8 -- (2)
  • 2 -- (2)
    • 4 -- (1)
    • 5 -- (2)
  • 3 -- (3)
  • 6 -- (4)

Wie man sieht, beginnt für jedes parent die Sortierung von pos neu.

Ein typischer Aufruf sieht so aus:
Code:
<?php
require_once(WB_PATH.'/framework/class.order.php');
$order = new order(TABLE_PREFIX.'pages''position''page_id''parent');
if(
$order->move_down($page_id)) {
$admin->print_success($MESSAGE['PAGES']['REORDERED']);
} else {
$admin->print_error($MESSAGE['PAGES']['CANNOT_REORDER']);
}
Die Seite mit der page_id $page_id wird innerhalb der lokalen Sortierung von parent um eins nach unten verschoben, d.h. das Feld position wird angepaßt.

Um eine flache Liste zu sortieren, d.h. eine Liste ohne parent-Element, kann man als letztes Argument 1 übergeben:
Code:
<?php
require_once(WB_PATH.'/framework/class.order.php');
$order = new order(TABLE_PREFIX.'mod_module_users''pos''user_id'1);


$order = new order($table, $field_pos, $field_id, $field_parent);
$table - die Datenbank-Tabelle, in der neu sortiert werden soll.
$field_pos - Das Feld, daß die Position (die Sortierung) enthält.
$field_id - Das Feld, daß die ID enthält. Dies ist z.B. die page_id, section_id, ... also die ID, die den Datenbank-Eintrag eindeutig bezeichnet (oft der PRIMARY KEY).
$field_parent - Das Feld, dessen "Unter-Liste" sortiert werden soll. Für flache Listen 1 verwenden.

Methoden:

move_up($id) - Den Eintrag mit $id um einen nach oben verschieben

move_down($id) - Den Eintrag mit $id um einen nach unten verschieben

get_new($parent) - Liefert die nächste freie Position in der "Unter-Liste" von parent. Für flache Listen hier 1 übergeben.

clean($parent) - Nach dem löschen eines Eintrages aus einer Liste müssen alle Einträge neu durchnummeriert werden. Dazu dient diese Methode. parent gibt wiederum die "Unter-Liste" an. Für flache Listen hier 1 übergeben.


thorn.
« Last Edit: September 02, 2010, 06:24:35 PM by thorn » Logged

Pages: [1]   Go Up
Print
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.16 | SMF © 2011, Simple Machines Valid XHTML 1.0! Valid CSS!