Scriptorama.nl

Header image showing a keyboard, mouse, laptop and books on design patterns

Voorkom email header injection

In de posting die ik laatst deed over de nieuwe versies van PHP 4 en 5 schreef ik over dat je (tot PHP 5.1.2) via het Session ID extra HTTP headers mee kon sturen en daarmee de nodige nare trucjes mee kon uithalen. In principe bestaat dit probleem ook in ander onderdeel van PHP, alleen, deze keer is het niet perse de schuld van PHP.

De voorbeelden maken gebruik van de HttpUtility klasse die beschreven staat in Tips voor een veiligere site

Als je de functie mail() niet op een veilige manier gebruikt, kunnen vervelende mensen er op een bijna zelfde manier misbruik van maken. Dat er vervelende dingen mee gebeuren is ook duidelijk; op mijn website kreeg ik regelmatig mailtjes waarin duidelijk te zien was dat er geprobeerd was om te knoeien met de mail headers (al lijken ze er nu weer mee te zijn opgehouden). Het probleem is ook niet nieuw. Misschien ken je het script FormMail wel, met zijn niet al te beste reputatie, waarmee al de nodige internet bewoners mee lastig is gevallen.

Het klassieke voorbeeld is natuurlijk het contact formulier. Zelfs daarmee kan het al misgaan. Als er niet gecontroleerd wordt of de waarden die binnenkomen van het formulier correct zijn, kunnen mensen zelf gaan spelen met de inhoud met alle spammige gevolgen van dien.

PHP:
  1. require 'HttpUtility.class.php';
  2.  
  3. if ($_SERVER['REQUEST_METHOD'] == 'POST')
  4. {
  5.   $veldNamen     = array ( 'naam', 'email' );
  6.   $onveiligeData = HttpUtility::haalPostData($veldNamen);
  7.  
  8.   /* We gaan hier even bewust de fout in door zonder controle
  9.    * data uit $onveiligeData te gebruiken */
  10.  
  11.   mail(
  12.     "info@example.org",
  13.     "Onderwerp",
  14.     "Iemand heeft een mailtje gestuurd",
  15.     "From: {$onveiligeData['naam']} <{$onveiligeData['email']}>\n"
  16.   );
  17.  
  18.   echo 'Bedankt voor je mailtje';
  19.   exit;
  20. }
  21.  
  22. /* toon de template met ons formuliertje. */
  23. require 'contact.tpl.php';

Normaal gesproken zou dit ongeveer het volgende mailtje opleveren:

CODE:
  1. To: info@example.org
  2. Subject: Onderwerp
  3. From: Test Gebruiker <test @example.org>
  4.  
  5. Iemand heeft een mailtje gestuurd.

Stel nu dat iemand, op een snode manier, er voor zorgt dat $onveiligeData['email'] de string waarde "test@example.org>\nTo: anderemail@example.org\n\nen dit is een ander bericht\n" bevat (\n is hier het escape karakter voor enter \n direct invoeren in een tekst veld gaat natuurlijk niet werken). Het mailtje wordt dan, omdat we niet hebben gecontroleerd, het volgende:

CODE:
  1. To: info@example.org
  2. Subject: Onderwerp
  3. From: Test Gebruiker </test><test @example.org>
  4. To: anderemail@example.org
  5.  
  6. dit is een ander bericht
  7. iemand heeft een mailtje gestuurd

Met een simpele ingreep heeft iemand dus zowel de ontvangers als het oorspronkelijke bericht gewijzigd! De oplossing hiervoor is simpel: een naam en een emailadres hoeven geen zogenaamde niet-toonbare karakters te kunnen bevatten. Door de functie ctype_print() te gebruiken kun je dus controleren of een emailadres / naam wel veilig te gebruiken is in een email bericht:

PHP:
  1. require 'HttpUtility.class.php';
  2.  
  3. if ($_SERVER['REQUEST_METHOD'] == 'POST')
  4. {
  5.   $veldNamen     = array ( 'naam', 'email' );
  6.   $onveiligeData = HttpUtility::haalPostData($veldNamen);
  7.   $veiligeData   = array();
  8.   $bevatFouten   = false;
  9.  
  10.   /* Verifieer dat het email uit 1 regel bestaat en
  11.    * ongeveer op een emailadres lijkt: */
  12.   if (!ctype_print($onveiligeData['email'])
  13.       || !preg_match('#^.*@.*\.[a-z]+$#', $onveiligeData['email']))
  14.   {
  15.     echo "Het opgegeven emailadres bevat onjuiste karakters. Probeer het nog eens.";
  16.     exit;
  17.   } else {
  18.     /* We hebben de data gecontroleerd, dus we kunnen het nu gebruiken. */
  19.  
  20.     $veiligeData['email']= $onveiligeData['email'];
  21.   }
  22.  
  23.   /* Controleer dat de naam uit 1 regel bestaat en uit:*/
  24.   if (!ctype_print($onveiligeData['naam'])) {
  25.     echo "De opgegeven naam bevat onjuiste karakters. Probeer het nog eens.";
  26.     exit;
  27.   } else {
  28.     /* We hebben de data gecontroleerd, dus we kunnen het nu gebruiken. */
  29.  
  30.     $veiligeData['naam'] = $onveiligeData['naam'];
  31.   }
  32.  
  33.   mail(
  34.     "info@example.org",
  35.     "Onderwerp",
  36.     "Iemand heeft een mailtje gestuurd",
  37.     "From: {$veiligeData['naam']} <{$veiligeData['email']}>\n"
  38.   );
  39.  
  40.   echo 'Bedankt voor je mailtje';
  41.   exit;
  42. }
  43.  
  44. /* toon de template met ons formuliertje. */
  45. require 'contact.tpl.php';

Kortom, verifieer altijd of de data die je ontvangt ook daadwerkelijk lijkt op het soort data dat je verwacht!

Reageer ook!

Ik heb gezocht naar een oplossing waarbij de ingevoerde gegevens direct geaccepteerd worden. Er verschijnt dus geen melding op het scherm, maar de gegevens worden wel gecontroleerd (en zonodig bewerkt).

De 'regex' lijkt me nogal uitgebreid. Kan het korter? Of efficiƫnter?

Leave a comment
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>