login  Naam:   Wachtwoord: 
Registreer je!
 Nota's op tutorial:

Tutorials > PHP > Beveiliging in scripts
Pagina:

Reacties op de tutorial Beveiliging in scripts


Offline  Wijnand
Gepost op: 22 december 2008 - 09:27
Moderator

Citaat:
mysql_query("SELECT * FROM `gebruikers` WHERE `usr_id` = '".$_GET['id']."'");

Nu, op het eerste zicht lijkt er helemaal niets mis met deze code. Maar toch is deze code gevaarlijk, stel je maar voor eens wat er zou kunnen gebeuren als de bezoeker niet braaf het ID in de url laat staan, maar daar zelf bijvoorbeeld «' OR '1'='1» invult, dan gaat je sql query er een stuk anders dan gepland uitzien!


Heb je dit getest? Want omdat je er single-quotes omheen heb gezet is het (volgens mij) niet mogelijk om deze query via $_GET['id'] aan te passen.

het geld wel bij: mysql_query("SELECT * FROM `gebruikers` WHERE `usr_id` = ".$_GET['id']."");

Offline  Koen
Gepost op: 22 december 2008 - 11:22
PHP expert

Wijnand schreef:
[..quote..]

Heb je dit getest? Want omdat je er single-quotes omheen heb gezet is het (volgens mij) niet mogelijk om deze query via $_GET['id'] aan te passen.

het geld wel bij: mysql_query("SELECT * FROM `gebruikers` WHERE `usr_id` = ".$_GET['id']."");


Hallo Wijnand,

Ik heb dit wel degelijk getest, en het werkt, zoals het zou moeten 
Hieronder de resultaten:
  1. <?php
  2. mysql_connect('localhost', '', '');
  3. $id = stripslashes($_GET['id']); // magic_quotes staat aan bij mij..
  4. $sql = mysql_query("SELECT * FROM `2l2d_users` WHERE `usr_id` = '".$id."'");
  5. while($arr = mysql_fetch_assoc($sql)) {
  6. echo $arr['usr_nick'].'<br />';
  7. }
  8. ?>


Citaat:
a
aaaqq
aaasbsg


=> Het werkt 
Dit komt dus omdat we de single quote die we geopend hadden, weer sluiten door een tweede single quote mee te geven in de URL.

Mvg,

Koen

EDIT: Url was index.php?id=' OR '1'='1

Offline  Erwt
Gepost op: 23 december 2008 - 09:41
PHP beginner

Ik vind het een zeer nette tutorial, niet perfect maar 5/5 omdat er weinig vergelijkingsmateriaal op SiMa is Bedankt!

Een tutorial over die 'logs' lijkt me ook wel intressant 

Offline  Koen
Gepost op: 25 december 2008 - 22:12
PHP expert

Bedankt, Erwt 

Wanneer ik weer eens tijd heb zal ik een stukje schrijven over de logs!

Mvg,

Koen

Offline  Chillobillo
Gepost op: 26 december 2008 - 11:30
Lid

Prima tutorial, nooit geweten hoe het precies zat allemaal. Nu wel. Heel erg bedankt!

Offline  Rik
Gepost op: 28 december 2008 - 11:45
Gouden medailleGouden medaille

Crew algemeen


Goede duidelijke tutorial!

Alleen dat stukje over vraagtekens bij inclusion klopt dat wel? Je kunt in ieder geval geen vraagtekens in bestandsnamen gebruiken, maar php negeert ze toch niet? Volgens mij moet je daar ook de null byte %00 voor gebruiken...

Offline  Koen
Gepost op: 28 december 2008 - 12:50
PHP expert

Boukefalos schreef:
Goede duidelijke tutorial!

Alleen dat stukje over vraagtekens bij inclusion klopt dat wel? Je kunt in ieder geval geen vraagtekens in bestandsnamen gebruiken, maar php negeert ze toch niet? Volgens mij moet je daar ook de null byte %00 voor gebruiken...

Klopt!

Het vraagteken is enkel van toepassing bij RFI.

Heb het bij deze aangepast 

Koen

Offline  Martijn
Gepost op: 29 december 2008 - 11:41
Crew PHP

wel apart. Ik had een script gemaakt die een post of get in 1x beveiligd, en die krijgt een rating van 0.5, en hier word gedaan en niets over gezegd....

naja, verder wel handig tut'je

Offline  Richard
Gepost op: 14 januari 2009 - 01:39
Crew algemeen

Mag ik vragen of, en zo ja, waarom je in deze tutorial aanraadt om htmlspecialchars over data te gooien die in de database komt?

Offline  Abbas
Gepost op: 05 februari 2009 - 18:02
Gouden medaille

Crew .NET


Echt een hele informatieve en duidelijke tutorial! 

Offline  Hend
Gepost op: 02 oktober 2010 - 22:26
Lid

Citaat:
mysql_query("SELECT * FROM `gebruikers` WHERE `usr_id` = '".$_GET['id']."'");

Heb ik ook alleen bij mij werkt het niet omdat er bij mij nog LIMIT 1 bij staat

Offline  Thomas
Gepost op: 10 januari 2014 - 15:09
Moderator

Quotes om variabelen is toch een beetje security through obscurity. Naar mijn mening is input filtering en output escaping afdoende. Het toevoegen van quotes (EDIT: om numerieke waarden) is niet nodig als je input filtert:

  1. <?php
  2. // filter input
  3. if (isset($_GET['id']) && is_numeric($_GET['id'])) {
  4. $query = 'SELECT ...
  5. FROM ...
  6. WHERE id = '.mysql_real_escape_string($_GET['id']); // escape output
  7. }
  8. mysql_query($query) or die('sigh');
  9. ?>


In het bovenstaande voorbeeld is het misschien niet eens nodig om de output te escapen, maar doorgaans is het makkelijker om dit toch altijd te doen, dan hoef je ook niet meer elke keer de afweging te maken of het nodig is of niet (en hier mogelijk later weer op terugkomen ).

EDIT: Om strings moeten uiteraard wel quotes staan, niet vanwege security, maar zodat je SQL-syntax klopt...

htmlentities() vs htmlspecialchars()
Ik ben hier nog steeds niet helemaal uit, ik gebruik voor nu htmlspecialchars() voor escaping. Is er (in specifieke gevallen?) een reden om htmlentities boven htmlspecialchars te gebruiken, of andersom? In beide gevallen loont het in ieder geval de moeite om expliciet een karakter-encoding in te stellen (derde parameter in beide functies), ten einde vage problemen te voorkomen.

Offline  Thomas
Gepost op: 22 augustus 2014 - 14:14
Moderator

Heb mij ondertussen zelf een beetje bijgeschoold in dit soort (beveiligings)onderwerpen. Naar aanleiding daarvan kan ik het volgende zeggen over deze tutorial: deze is inmiddels (begrijpelijk) gedateerd (heb de datum uit de database gevist: deze stamt uit december 2008) en op sommige punten ook niet helemaal juist. Ik zal proberen alles zo goed mogelijk te onderbouwen.

In eerste instantie is het heel belangrijk dat je begrijpt waarom je iets doet, eigenlijk nog meer dan wat je doet. Je hebt dan namelijk een onderbouwing (een verklaring) van hetgeen je doet, wat je verder kan sterken in het begrip van de materie.

Om maar meteen bij het einde te beginnen, het "enige" mantra wat je nodig hebt is: filter input, escape output. Natuurlijk moet je wel begrijpen wat dit inhoudt zodat je weet hoe je dit kunt toepassen. Laten we dit principe toepassen op de behandelde onderwerpen (in eerste instantie alleen voor SQL injection):

SQL injection
input filtering
Het "probleem" is hier dat DATA behandeld wordt als SQL (de twee worden niet goed uit elkaar gehouden zodat je vrij SQL bij je DATA kunt stoppen en zo de werking van de query kan manipuleren).

Het toevoegen van quotes is, zoals al eerder aangegeven, toch een beetje security through obscurity: je legt de drempel om iets te kraken / te omzeilen iets hoger zonder het achterliggende probleem weg te nemen. Idealiter houd je je beveiliging zo simpel mogelijk en transparant. addslashes(), stripslashes() etc. maken de boel alleen maar complexer. Daarbij pas je je invoer ook aan, wat niet erg handig is als je deze later weer wilt bewerken (tenzij je weer door deze hoepel van slashes-manipulatie springt). Daarbij gaat magic_quotes_gpc verdwijnen, wat weer een heleboel andere (potentiële) problemen met zich meebrengt. Het is een stuk eenvoudiger om niet uit te gaan van automatische escaping, en hier ook geen gebruik van te maken.

Om terug te komen op het achterliggende probleem: het probleem is dat invoer niet wordt gecontroleerd op een voorgeschreven vorm (oftewel: invoer wordt niet gevalideerd). Dit is precies wat input filtering behelst: controleren of het voldoet aan een bepaalde vorm / bepaalde condities.

Als je een getal verwacht voor gebruik in een query, controleer hier dan op. Als de invoer niet voldoet aan de criteria, voer dan de query in het geheel niet uit.

Hierbij moet je wederom snappen hoe een en ander in PHP geregeld is. Alle data in $_POST, $_GET etc. zijn van het type string. Je kunt dus niet met is_int() controleren of $_GET['id'] een numerieke waarde bevat want deze functie controleert het type.

is_numeric() accepteert ook exponenten, decimalen, octale, hexadecimale en binaire waarden, dit is wellicht te breed.

ctype_digit() werkt in principe goed, maar "werkt" alleen (op de manier zoals je dit zou verwachten) voor strings. Als je een variabele van het type integer hebt levert het uitvoeren van ctype_digit() op zo'n variabele mogelijk onvoorspelbaar gedrag.

ereg() is vanaf PHP 5.3.0 deprecated.

Rest daar nog preg_match(). Als je een numerieke waarde uit je URL wilt vissen betreft dit vaak de identificerende kolom van een database-tabel. Toegestane waarden van zo'n (auto-increment) index zijn positieve gehele getallen (groter dan 0). Een functie met een reguliere expressie die deze lading precies dekt is bijvoorbeeld:
  1. <?php
  2. function isIndex($in) {
  3. return preg_match('#^[1-9][0-9]*$#', $in);
  4. }

Deze functie accepteert precies die waarden die je zou willen toestaan.

Tegenwoordig zijn er ook filter_ functies die je voor dit soort doeleinden kunt gebruiken. Deze functies lijken wel de types van de gecontroleerde variabelen te veranderen...

output escaping
Okay, je data is succesvol gevalideerd, en nu kan het deel uit gaan maken van je query (als DATA). Nu kan het nog steeds voorkomen dat gevalideerde data binnen de MySQL context karakters bevat die een speciale betekenis hebben. Je wilt echter nog steeds dat deze data enkel als DATA binnen je SQL-statement behandeld wordt. Daarvoor zijn functies als mysql(i)_real_escape_string(). Het lijkt overkill om een variabele die als "index" gevalideerd is te escapen met een _real_escape_string(), maar toch zijn er argumenten om al deze DATA elementen (toch) altijd te escapen:
- het draagt bij aan een grotere bewustwording (onderscheid tussen / scheiding van DATA en SQL)
- je hoeft niet elke keer de afweging te maken of je nu wel of niet hoeft te escapen, waarbij je in het ongunstigste geval dit een keer vergeet waar het toch verstandiger was geweest om dit wel te doen...

(Dit gaat uiteraard over MySQL en MySQLi, we hebben het dan dus niet over PDO met geparameteriseerde queries waar trouwens ook wel het een en ander over gezegd kan worden... type checking = LOL)

Het woordje context is zojuist voorbij gekomen. Dit is o-zo belangrijk bij output escaping. Afhankelijk van WAT je escaped, moet je dit op een BEPAALDE MANIER doen. Het voorstel in de tutorial om een soort van gecombineerde superfunctie te maken voor zowel escaping voor database-operaties alsmede HTML-output is zeer onverstandig zoniet pertinent onjuist.

Het escapen van MySQL DATA en het escapen van data in een HTML-context zijn twee (compleet) verschillende dingen. Daarbij is addslashes() NIET VRIJ INWISSELBAAR voor een _real_escape_string() functie en vice versa.

Tevens hangt escaping af van je character encoding. Je HTML output en je database maken mogelijk gebruik van verschillende soorten character encoding. "Escape-functionaliteit" werkt alleen goed als de juiste character encoding staat ingesteld. Voor MySQL doe je dit met een _set_charset() functie en ook kun je een character encoding aangeven in htmlentities() (of wellicht beter) en htmlspecialchars(). Let er ook op dat in de twee laatstgenoemde functies de DEFAULT character encoding in PHP 5.4.0 VERANDERT naar UTF-8, dus mogelijk werkt dan escaping in oude scripts niet meer goed...

@todo:
- algemene opmerking over whitelisting (dit is in feite wat je doet met input filtering)

Mogelijk ontbrekende onderwerpen:
- CSRF (remedie: tokens)
- MIME header injection (remedie: input filtering)

Offline  Thomas
Gepost op: 23 augustus 2014 - 14:57
Moderator

Koen schreef:
Als je hier dus gekomen bent om te leren hoe je kan inbreken bij websites, ga dan maar gezellig ergens anders naartoe, hier wordt enkel uit de doeken gedaan hoe je je scripts het beste kan beveiligen.
Maar daartoe moet je toch juist weten hoe iemand in kan breken? 

Koen schreef:
Met de sql injection kan men onder andere mysql commando's uitvoeren, hele tabellen verwijderen, meerdere queries tegelijk uitvoeren, inlog-systemen omzeilen, enzovoort.
Deze vlieger gaat meestal niet op omdat de mogelijkheid om tegelijkertijd meerdere queries uit te voeren meestal niet actief is. Het inschakelen hiervan heeft op zichzelf al een heleboel implicaties qua security, je moet dan namelijk heel anders met je queries om gaan springen.
Wel kan de werking van de (enkele) uit te voeren query gemanipuleerd worden.

Zoals eerder aangegeven:
Quotes rond al je values is geen (goede) oplossing. Hiermee pas je tevens je invoer aan, wat doorgaans geen goed idee is. Je introduceert hiermee enkel extra werk: die quotes moeten er op een gegeven moment ook weer af. Daarnaast zorgt dit voor onnodige overhead bij opslag.
Quotes escapen is geen (goede) oplossing. Wat je eigenlijk wilt is een algemene escaping in een bepaalde context die ervoor zorgt dat de speciale betekenis van bepaalde karakters in deze context ongedaan wordt gemaakt. Hier vallen quotes meestal ook onder, maar niet uitsluitend. Het zou dan (dus) ook vreemd zijn om deze apart te behandelen. Gebruik daarom escape-functionaliteit die specifiek is bedoeld voor de context: ..._real_escape_string() voor MySQL, htmlspecialchars() voor HTML.

XSS injection
Kan volgens mij compleet voorkomen worden door output escaping (gebruik htmlspecialchars() met de juiste character encoding op AL je uitvoer)? Je kunt ook hele rigoreuze controles uitvoeren op je invoer, maar soms wil je ook gewoon HTML als invoer toestaan. Daarnaast wil je dit niet in "escaped" vorm opslaan, want dan wordt het een ramp om dit later weer te bewerken. Het beste is gewoon om invoer in zijn oorspronkelijke (rauwe) vorm op te slaan. Dan is het wel des te belangrijker dat je bij het afdrukken ervan output escaping toepast...

Een generieke functie voor het escapen van (HTML) output is bijvoorbeeld (aanname: character encoding is UTF-8):
  1. <?php
  2. function escape($in) {
  3. return htmlspecialchars($in, ENT_QUOTES, 'UTF-8');
  4. }
  5. ?>


Inclusion
Lekken hierin komen meestal door gebrekkige/afwezige input filtering: je controleert niet (nauwkeurig genoeg) wat je include. Definieer daartoe een whitelist: zet hierin expliciet de toegestane waarden. Een mogelijke implementatie hiervan is een switch-statement zoals Koen in de tutorial laat zien. Maar andere varianten zijn ook mogelijk (lookup van een pagina "slug" in een database).

Uploading
Controleren van MIME type alleen is niet genoeg. Volgens mij is het zo dat je browser bij het uploaden van een bestand enkel kijkt naar de extensie, op grond daarvan wordt een MIME type bepaald. Stel dat ik een bestand hack.php heb, en deze simpelweg hernoem naar hack.jpg. Vervolgens upload ik dit bestand in een systeem die alleen controleert op MIME type (lees: extensie) en vervolgens vind ik een manier op dit bestand te includen. Hack geslaagd. Als je een afbeelding verwacht, behandel het geuploade bestand dan als zodanig. Gebruik bijvoorbeeld een functie als getimagesize(). Roep deze aan met een "@" om fouten te onderdrukken. Als deze functie false retourneert, is het geassocieerde bestand waarschijnlijk geen bestand dat kan worden geïdentificeerd als een afbeelding...

Offline  Thomas
Gepost op: 27 november 2015 - 14:43
Moderator

Nog enkele notities:

Richard merkt ook terecht op of/waarom er input ge-escaped moet worden als je iets wegschrijft naar je database. Dit is in het algemeen een slecht idee. Het is dan ook op elk moment onduidelijk of dit nu wel of niet gedaan is. Spreek af dat je escaping zo laat mogelijk doet, dus bijvoorbeeld net voor het weergeven op je scherm. Daarbij is het waarschijnlijk een verspilling van geheugen en CPU-cycles om elke keer de escaping uit te voeren, en daarna de omgekeerde bewerking (die ook nog eens foutgevoelig kan zijn) uit te voeren.

Pagina:

Enkel aanvullende informatie is welkom. Geen prijzende of afkeurende reacties.
 
© 2002-2024 Sitemasters.be - Regels - Laadtijd: 0.156s