Vertrouw nooit gebruikersinvoer

Deze blogpost is deel 6 van 8 in de reeks Web development uitdagingen

Gisteren en vandaag heb ik na het zien van een stukje code dat ergens anders ontbrakĀ  gebruikt om me eens flink te verdiepen in de veiligheid van PHP. En dan vooral de veiligheid van gebruikersinvoer want dat je die niet zonder meer mag vertrouwen is me de afgelopen maanden al wel duidelijk geworden.

En het was maar goed dat ik me erin verdiepte. Er ontbrak dus ergens een stukje code. In mijn code voor het gastenboek dat binnenkort hopelijk op Web development uitdagingen verschijnt. Dat kan tot het onderstaande resultaat leiden:

gehackt

Hoe heb ik deze ‘hack’ bewerkstelligd? Op de plek waar ik een bericht in kon vullen, heb ik simpelweg het volgende ingevuld:

<script>alert('Gehackt!');</script>

Dat was alles. Maar naar natuurlijk zijn er gevaarlijkere manieren om deze kwetsbaarheid te exploiteren. Je zou bijvoorbeeld een script van een andere pc kunnen laden en met met dat script die ander ongemerkt toegang geven tot jouw computer. De techniek heet Cross-site scripting (XSS).

Kwetsbaarheid oplossen

Gelukkig is de kwetsbaarheid die ik hier liet zien betrekkelijk eenvoudig op te lossen. Het gaat erom dat de PHP engine op de server te gevaarlijke code niet meer als zodanig interpreteert. Aanhalingstekens en haken moeten daartoe omgezet worden in ongevaarlijke tekens. Meerdere mogelijkheden, ik noem er drie:

  1. strip_tags.() Deze functie zorgt er in bovenstaande voorbeeld voor dat de script tags verwijderd worden waardoor het voorbeeld slechts een onschuldige tekst oplevert.
  2. htmlentities(). Deze functie zorgt ervoor dat alle karakters waarvoor de functie aangeroepen wordt, omgezet wordt naar HTML code.
  3. htmlspecialchars() Doet hetzelfde als nummer 2, maar behoudt accenten. Die worden door htmlentities() namelijk omgezet in HTML code.

Zelf geef ik de voorkeur aan htmlspecialchars(). Hoe maak je de code nu veilig? Ik geef de code die berichten uit het gastenboek laat zien.

function show_post($result)
		{
			
			foreach ($result as $key => $value)
				{
					echo '<div class="box">';
					foreach ($value as $subkey => $subvalue)
						{
							$subkey = htmlspecialchars($subkey);
							$subvalue = htmlspecialchars($subvalue);
							echo '<div class="' . $subkey . '">' .  $subvalue . '</div>';	
							if($subkey == "id")
								{
									echo '<a href="update.php?id=' . $subvalue . '"><div class="update"></div></a>';
									echo '<a href="delete.php?id=' . $subvalue . '"><div class="delete"></div></a>';
								}
						}
					
					echo '</div>';
				}

In de oorspronkelijke code ontbraken regels 9 en 10. Deze regels zorgen er nu voor dat de gebruikersinvoer gecontroleerd wordt voordat deze op het scherm getoond wordt.

Nog een kwetsbaarheid

Bij formulieren is het gebruikelijk om aan te geven welke method (get of post) er gebruikt wordt. En welke pagina de afhandeling verzorgt (action). Het action attribuut kan sinds HTML5 weggelaten worden. In dat geval is de huidige pagina de pagina die het formulier afhandelt.

Je kunt ook kiezen voor een stukje PHP. De functie $_SERVER[‘PHP_SELF’] is huidige pagina. Die kun je als volgt opnemen in je action.

<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">

Met htmlspecialchars dus. Doe je dat niet dan kun je met de volgende tekst achter de url in de adresbalk hetzelfde ‘Gehackt’ scherm te zien krijgen als waar we mee begonnen.

/%22%3E%3Cscript%3Ealert('Gehackt!')%3C/script%3E

Het laatste voorbeeld is overigens afkomstig van W3Schools. Welbestede dagen, zou ik denken. Eenvoudig te testen veiligheid.

Wordt ongetwijfeld vervolgd, al is het maar met een aflevering over gebruikersinvoer en de database.