Bestanden & security (updated)
Het programmeren van een veilige website is best wel een klus: Scriptorama heeft er al een paar artikelen over geschreven en dan hebben we nog lang niet alles besproken. Vandaag kijken we eens naar het gebruik van bestanden met bijvoorbeeld GD maar ook met de include-functie.
Header, content, Footer
Als je onze vorige artikelen hebt doorgelezen, dan weet je misschien al het nodige over de veiligheids problemen die kunnen ontstaan. Een van de vuistregels is:
Verifieer dat de data die je ontvangt in het formaat is dat je verwacht.
Helaas doen veel mensen dat wel bij het ene, maar vergeten ze het compleet bij het andere. Of ze zijn niet voldoende op de hoogte van het gedrag van functies die ze gebruiken met onveilige data en dat leidt weer tot mogelijke problemen.
Laten we het all-time-favorite gebruik van include eens bekijken bijvoorbeeld. Wat veel mensen willen is dat ze een keer een pagina kunnen definieren en dan afhankelijk van wat wordt meegegeven aan de URL kunnen bepalen wat de daadwerkelijke inhoud is. Dan komen ze in het begin meestal op iets als:
Als je dergelijke code bij een vraag op een forum plaatst beginnen veel mensen vaak al te steigeren (gelukkig!) want op deze manier kun je zo ongeveer alles doen wat je juist niet wilt dat er gebeurd. Je kunt een willekeurig tekst bestand opgeven op de server, zoals bijvoorbeeld /etc/passwd. Enger: je zou ook een URL kunnen opgeven waar PHP code in staat: http://localhost/mijncode.txt. Deze code wordt dan gewoon uitgevoerd!
Wat mensen dan vaak zeggen is dat je er voor moet zorgen dat er ".php" achterstaat, dan zou je nooit een bestand als /etc/passwd kunnen opgeven:
-
$contentPage = $rawContentPage . '.php';
-
-
include $contentPage;
Dat is nu precies waar het dan alsnog fout gaat: de include functie is namelijk niet binary safe. Als er binaire gegevens worden meegegeven in de bestandsnaam, waarbij het in dit geval gaat om het karakter NULL, zal de include functie de opgegeven bestandsnaam enkel lezen tot dit NULL karakter en de rest vergeten. Het volgende voorbeeld illustreert dit:
-
/* We geven een onschuldig bestand mee en plakken daarachter een NULL karakter. Dit
-
* is ook mogelijk voor de URL, maar hoe dat precies werkt laten we over aan de lezer
-
* als eh, oefening ;-)
-
*/
-
$page = "/etc/motd\x00";
-
-
$contentPage = $rawContentPage . '.php';
-
-
// $contentPage bevat nu de string "/etc/motd\x00.php"
-
-
include $contentPage;
Als we deze code vervolgens uitvoeren:
-
$ php include-test.php
-
Linux loina 2.6.15-16-386 #1 PREEMPT Mon Feb 20 16:38:26 UTC 2006 i686 GNU/Linux
-
-
The programs included with the Ubuntu system are free software;
-
the exact distribution terms for each program are described in the
-
individual files in /usr/share/doc/*/copyright.
-
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
-
applicable law.
.. zie je dat het MOTD bestand (Message Of The Day) van mijn PC door PHP geincludeert en,omdat het geen PHP code bevat, direct vertoont is! Je ziet vast wel wat voor vervelende dingen je hiermee kunt uithalen! Een ander voorbeeld is bijvoorbeeld het gebruik van GD. Deze module heeft ook de nodige functies die met bestanden werken. In een topic op PHPFreakz stond bijvoorbeeld de volgende code:
-
$filename = $_POST['titel'] . '.jpg';
-
ImageJpeg($rImage, $filename);
Wederom zie je dat gedacht wordt dat je door een extensie er achter te plakken veilig bent, maar niets is minder waar:
-
$titel = "/tmp/mijnbestand.sh\x00";
-
$filename = $titel . '.jpg';
-
-
$rImageResource = ImageCreateTrueColor(10,10);
-
ImageJpeg($rImageResource, $filename);
Als we dit uitvoeren:
-
mathieu@loina:~ $ ls -la /tmp/mijnbestand.sh
-
ls: /tmp/mijnbestand.sh: No such file or directory
-
-
mathieu@loina:~ $ php4 gd-test.php
-
-
mathieu@loina:~ $ ls -la /tmp/mijnbestand.sh
-
-rw-r--r-- 1 mathieu mathieu 695 2006-03-16 12:35 /tmp/mijnbestand.sh
Toegegeven, op een Unix-achtige machine, zoals Linux of FreeBSD zul je hier niet heel veel mee kunnen uithalen omdat het bestandssysteem niet zomaar toestaat dat je naar bestanden van anderen gaat schrijven. Persoonlijk zou ik toch wat rustiger slapen als ik zeker wist dat iedereen die op mijn website komt ook niet in staat is om dat zomaar te PROBEREN.
Dus, hoe lossen we dat op?
De oplossing is dus nogmaals:
Verifieer dat de data die je ontvangt in het formaat is dat je verwacht.
In het geval van het includen van een bestand is de gegeven oplossing vaak het gebruik van een switch statement of bijvoorbeeld een array met mogelijke bestanden:
-
/* Gewone strings in PHP zijn in de meeste gevallen WEL binary safe: */
-
-
$rawContentPage = "";
-
-
{
-
$contentPage = $_GET['page'] . '.php';
-
} else {
-
$contentPage = "home.php";
-
}
-
-
include $contentPage;
-
-
?>
Maar dat is misschien niet flexibel genoeg. Nu moet je iedere keer als je een pagina toevoegt dus prutten in je code. Wat je ook kunt doen is van te voren bepalen dat alle pagina bestand namen alleen mogen bestaan uit letters met hooguit een underscore. Wanneer je hier vervolgens op gecontroleerd hebt kun je de gegevens veilig gebruiken:
-
-
-
// Iets of iemand probeert iets fouts mee te geven
-
$unsafeContentPage = 'home';
-
}
-
-
$contentPage = $unsafeContentPage . '.php';
-
-
}
-
-
// we hebben onze $unsafeContentPage geverifieerd, dus we mogen hem gebruiken
-
-
include CONTENT_PATH . $contentPage;
De zelfde methode kun je gebruiken op het GD voorbeeld:
-
$titel = "";
-
-
{
-
}
-
-
$filename = $_POST['titel'] . '.jpg';
-
-
ImageJpeg($rImage, $filename);
Conclusie
Tja, de conclusie is simpel. Verifieer dat de data die je ontvangt in het formaat is dat je verwacht. ;-)
Update 09/04/2007: Code voor dit artikel is aangepast naar aanleiding van het volgende artikel: Quick tip: waarden controlen met preg_match() in PHP
Volg Scriptorama via RSS!
Reageer ook!
Als systeembeheerder zorg je er toch voor dat er geen bestanden buiten de webroot geincludeerd kunnen worden door voor elke vhost een open_basdir te configureren.
Door Daniel
op 03.18.06 @ 1:23 pm | Permalink
/etc/motd is maar een voorbeeld; je kunt ook URLs includen op dergelijke wijze en dat is een stuk enger :-)
Gelukkig hebben we in PHP6 daarvoor allow_url_include, enzo.
Door Mathieu
op 03.18.06 @ 1:25 pm | Permalink
Dat is inderdaad waar en dt is heel eng.. al kun je door een openbasedir restrictie niets op het onderliggende systeem doen, je zult wel gemakkelijk achter bijvoorbeeld de database gegevens kunnen komen. Checken of je de input krijgt die je verwacht is inderdaad heel belangrijk, maar op een shared hosting platform waar je te maken hebt met duizenden script kopieerende, copy-pastende uilskuikens die net aan het woord PHP kunnen spellen moet je de risico's zo veel mogelijk beperken. Bijvoorbeeld door geen streams toe te staan, safe mode en open_basedir.
Door Daniel
op 03.18.06 @ 1:30 pm | Permalink
Je hebt gelijk, maar dat is niet iets waar ik als scripter op wil bouwen. Het belicht namelijk niet alle mogelijke situaties. Je kunt ook een eigen server hebben waar je wel met streams wilt werken, bijvoorbeeld.
Zoals ik zei in het artikeltje:
"Persoonlijk zou ik toch wat rustiger slapen als ik zeker wist dat iedereen die op mijn website komt ook niet in staat is om dat zomaar te PROBEREN."
:)
Door Mathieu
op 03.18.06 @ 1:33 pm | Permalink
Je hebt helemaal gelijk en ik ben het er helemaal mee eens. Geen mogelijkheid creeren voor bezoekers iets uit te proberen is altijd het beste.
Helaas maak ik af en toe zorgwekkende dingen mee. Lekken in mailscriptjes, zeer inefficient geschreven queries en ook zaken zoals in het artikel beschreven zijn.
Heeft iemand een eigen server dan is dat zijn eigen verantwoording, maar op een shared hosting platform wil je de gebruikers toch tegen zichzelf beschermen.
Door Daniel
op 03.18.06 @ 1:39 pm | Permalink
$rawContentPage
Deze variabele is niet gedefinieerd.
Correct me if i'm WRONG, maar bij mij werkt het pas als ik $rawContentPage vervang met $unsafeContentPage
Of sloop ik het hiermee?
Bij mij werkt het zo namelijk wel, en als ik iets anders invul dan letters en cijfers, dan retourneert hij Home.
Thanks voor dit inzicht!
Marinus de Beer
ps mail me als dit dwars zit!
Door mARIN~1.txt
op 12.20.06 @ 3:08 pm | Permalink
Jup $rawContentPage moet $unsafeContentPage zijn. Iets halverwege in het artikel veranderd maar niet overal doorgevoerd.
Thanks for the heads up.
Door Mathieu Kooiman
op 12.20.06 @ 3:50 pm | Permalink
Waarom een array? Zijn alle mensen isfile() vergeten?!
Door Saviro
op 04.07.07 @ 5:22 pm | Permalink
Nou, nee, er staat nog een voorbeeld onder dat stukje code waarin file_exists() gebruikt wordt?
Door Mathieu Kooiman
op 04.09.07 @ 12:02 pm | Permalink
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>