[Home/Nieuws]  [Magazines]  [Meetings]  [Downloads]  [Redaktie]  [Geschiedenis]

Website beveiliging, of het gebrek daar aan
Trojan Eel

Dit document is hoofdzakelijk bedoeld voor mensen die webpagina's maken (vooral webapplicaties) om ze te wijzen op een paar voor de hand liggende lekken die kunnen ontstaan als deze niet voldoende voorkomen worden, ook al zullen er vast ook mensen zijn die er misbruik van willen maken, waarvoor ik uiteraard geen verantwoordelijkheid neem :) (#include) Dit document is absoluut niet opgezet als volledige gids van alle mogelijke lekken die er mogelijk zijn via de webserver, en bevat enkel veel voorkomende fouten. Als je echt bezorgd bent over beveiliging, houdt dan de advisories van de software die je gebruikt (bv Apache, Perl, PHP) goed in de gaten, en meldt je aan bij enkele beveiligingsmailinglists. Ik zal in document uitgaan van het gebruik van Apache, PHP en MySQL aangezien dit veel gebruikt wordt. Ook als je zelf andere software gebruikt (Perl, of misschien een andere database) zullen de principes die hier omschreven worden grotendeels hetzelfde zijn. Nu dit gezegd is, is de tijd daar om te beginnen.
Een van de belangrijkste punten. Als je gebruikersinput gebruikt (bijvoorbeeld bij een formulier) is het van het grootste belang dat deze input gecontroleerd wordt voordat deze gebruikt wordt. Een belangrijk voorbeeld hier is het gebruik van user input in database queries. Laten we bijvoorbeeld eens het voorbeeld nemen van gebruiker-validatie. Neem het volgende script:

<?php

php // $user en $password zijn door de gebruiker via formulier of wat dan ook opgegeven

$result = mysql_query("SELECT * FROM auth WHERE user='$user' AND password='$password'");

if ($result && mysql_num_rows($result)) {

// Login gelukt

} else {

// Login niet gelukt }

?>

Als zowel user en password correct is, zal de query 1 of meerde rows terug sturen (als het goed is maar 1, maar goed), in principe geen slechte manier van authenticatie (al is het misschien beter om de wachtwoorden eerst te versleutelen), maar het gebruikt ongecontroleerde user input! Als speciale tekens niet uitgefilterd worden kan de user bijvoorbeeld intypen: user: root'; SELECT * FROM auth WHERE 0 password: onbelangrijk


Als we dit nu in de query invullen krijgen we:

SELECT * FROM auth WHERE user='root'; SELECT * FROM auth WHERE 0 AND password='onbelangrijk'

Nu is het in (My)SQL mogelijk queries te scheiden met een ;, en voorgaande user input zorgde er dus voor dat de password check wegviel. Nu zou je eventueel de ; kunnen weghalen, maar daar ligt het probleem niet. Kijk maar:

user: root; password: bla' OR 1 OR 'bla

dit word: SELECT * FROM auth WHERE user='root' AND password='bla' OR 1 OR 'bla'

en is dus een query, waar de wachtwoord controle altijd 'waar' is (door de 1) en je dus de beveiliging kan omzeilen. Verder kunnen rare tekens ook SQL errors opleveren, bijvoorbeeld als iemand een ' in een veld zet (zonder bijbedoelingen). Maar goed, dit is uiteraard te voorkomen. Als deze voorbeelden bekeken worden ziet men dat er pas problemen komen zodra de user input uit een waarde gaat, en dus een ' stuurt. Deze is te escapen door een ' of een \ er voor te zetten, bijvoorbeeld door:

- gewoon string replacement te doen in user input

- mysql_escape_string() gebruiken, als dit beschikbaar is

- in PHP de magic_quotes optie te gebruiken

al is dit laatste af te raden om dat je dan geen controle meer hebt over welke variabelen wel en niet gequote zijn. Let wel op dat quoting niet dubbel gebeurd, bijvoorbeeld zowel met magic_quotes en string replacements, anders krijg je: ' wordt \' \' wordt \\' en \\ is gewoon een geescapete \, en dus heb je weer een losse ' Hier zie je trouwens een ander teken wat problemen op kan leven: het escape teken \, want behalve dat bijvoorbeeld \n door een enter vervangen wordt, is er ook weer een beveiligingslek, want als je in bovenstaand voorbeeld zet: user: root password: bla\' OR 1 OR \'bla

Dan wordt de escaping van ' ongedaan gemaakt, en bestaat het lek weer!

Het is zoals je merkt beter om alleen goede chars door te laten, dan om steeds nieuwe foute chars te stoppen, maar als je dit niet doet is het escapen van ' en \ wel het minimum. Een situatie die trouwens in PHP niet voorkomt (PHP decodeert) maar elders wel een rol kan spelen is het gebruik van %xx codes in de URL, om een bepaalde ASCII code mee te sturen. Als dit niet gedecodeerd wordt, kunnen bepaalde lekken ontstaan als het script tekst op tekens controleert en deze tekens geencode zijn.

PHP als CGI
Op het moment dat PHP als CGI gecompiled wordt, en in een webdirectory gezet wordt (bijvoorbeeld /cgi-bin/php) ontstaan er 2 belangrijke situaties

1. Het is mogelijk elke file op het systeem te bekijken (http://www.server.nl/cgi-bin/php?/etc/passwd)

2. Het is mogelijk door de webserver beschermde webpagina's te bereiken. Als je een bestand http://www.server.nl/private/index.php hebt, die met .htaccess of op een andere manier door de webserver beschermd wordt, zal de webserver eerst access controleren voordat hij PHP aanroept. Door PHP direct aan te roepen, dus via http://www.server.nl/cgi-bin/php/private/index.php, zal de webserver kijken of je /cgi-bin/php mag bekijken, en dus de andere beveiliging omzeilen.

De oplossing is natuurlijk simpel, gewoon niet de binary in een webdirectory zetten :-) .

- Specifiek: phpMyAdmin

phpMyAdmin (http://phpwizard.net/phpMyAdmin/) wordt vaak gebruikt om via het web databases te beheren. Het script is in principe zeer goed, en heel veilig, op een klein puntje na: Als je basis authenticatie gebruikt (naar de server toe) dan is login/wachtwoord van de database al ingevuld, en iedereen die het script opstart is automatisch ingelogd. Bij uitgebreide authenticatie geef je bij het opstarten een login/password op. De oplossing is, zoals vaak, simpel: Laat de webbrowser de directory afschermen (.htpasswd). Zie eventueel ook PHP als CGI, punt 2...

- Foutmeldingen in de browser

PHP toont standaard foutmeldingen in de browser, en vaak schrijven mensen code die SQL errors tonen in de browser. Let op dat deze foutmeldingen veel informatie kunnen geven over de structuur van de database/server (bestandsnamen, tabelnamen, veldnamen, etc.). Beter is het foutmeldingen te mailen naar jezelf, met informatie als URL e.d., of deze in een bestand wegschrijven.

- Openen/schrijven naar van bestanden

Twee punten:

1. Als je bij het openen het bestand door de gebruiker laat beïnvloeden, kan het zijn dat deze een geheel ander bestand opent, bijvoorbeeld door als naam /../../../../../etc/passwd op te geven. Het is belangrijk dat je controleert dat het doelbestand niet buiten een directory is. PHP heeft hier mogelijkheden voor (de doc_root optie, als ik me niet vergis)

- Authenticatie/Sessies

Voor het doorgeven van gebruiker/wachtwoord gegevens zijn een aantal mogelijkheden. De simpelste is waarschijnlijk HTTP authenticatie, die echter als nadeel heeft dat deze het wachtwoord in plain-text stuurt bij elk request, en dat hiervoor PHP als module moet werken. Een andere methode is het stoppen van user/password in een cookie of de URL. Het nadeel hiervan is dat, zelfs als beiden versleuteld worden, beiden altijd nog eventueel te achterhalen zijn (zie ook artikel betreffende webmail). De beste methode is waarschijnlijk het werken met sessies. De gebruiker logt in, het script controleert de gegevens en als deze kloppen wordt een uniek nummer aan de gebruiker toegewezen. Dit nummer wordt aan de browser als cookie (of in de URLs) gestuurd, en deze zal dit voortaan bij elke pagina mee sturen. De inlog gegevens, in combinatie met de sessie code, worden in een database opgeslagen. Op het moment dat een pagina, met sessie code, wordt opgeroepen worden de inlog gegevens uit de database gehaald, en gecontroleerd. Deze gegevens zijn nu niet voor de gebruiker beschikbaar, en worden ook minder heen en weer gestuurd.

Het enige wat verder moet gebeuren is de sessie database op te schonen, door bijvoorbeeld een uur na het laatste bezoek de sessie code te verwijderen, en de gebruiker opnieuw laten inloggen. Hiermee voorkom je ook dat iemand inlogt op een publieke computer, en daarna zomaar ik kan loggen zonder wachtwoorden.

Voor extra veiligheid zou in de sessie database eventueel ook nog gegevens kunnen staan als host en browser en zou een maximale sessie duur kunnen worden ingesteld, waarna de gebruiker sowieso opnieuw moet inloggen.

De informatie in 't Klaphek dient slechts een educatief doel. Gebruik van deze informatie zou strafbaar kunnen zijn. De redaktie wijst iedere verantwoordelijkheid voor gebruik door lezers van de in 't Klaphek opgenomen informatie af. De mening van een auteur weerspiegelt niet noodzakelijkerwijs de mening van de redaktie of uitgever.