Zend Form security (basics)

Ook bij een “gesloten” applicatie kun je nooit teveel doen aan de beveiliging van je formulieren. Voor een “standaard” omgeving wil je in iedere geval basisbescherming opzetten tegen;

  • Formulier spam
  • Fake/overmatige form posts
  • Ongewenste gegevens

Met Zend Framework heb je op zich al best wat leuke mogelijkheden tot je beschikking voor het valideren en filteren van inputvelden en een stukje bescherming tegen CSRF.

Formulier spam

Voor een eenvoudige site zal een spammer niet veel moeite doen. Enige wat je wilt is een drempel die het voor “standaardscripts” onmogelijk maakt je systeem te vervuilen en tegelijkertijd niet teveel werk is om te implementeren. Een mogelijkheid is het toevoegen van een veld wat je met CSS (of JavaScript) verbergt voor normale gebruikers en wat “false” is als deze een waarde bevat. Een normale gebruiker die geen CSS en JavaScript heeft aan staan is zeldzaam maar mocht die toch voorkomen dan kun je die eventueel met een label nog informeren over het feit dat dit een veld is speciaal om spambots uit te sluiten.

Fake/overmatige form posts

Zend_Form_Element_Hash biedt hiervoor op zich in basis een goede bescherming tegen. Bij het aanmaken van je form neem je een veld op met het type “hash”. Dit veld kun je van parameters voorzien zoals een timeout etc. Bij het aanmaken van het form maakt Zend een session aan met deze hash erin. Bij de post van het form wordt deze gevalideerd tegen de hash. Als hij verlopen is geeft de form isValid false terug. Hier zit wel gelijk een “let op!” momentje in; als je in je structuur ergens de ongelukkige setup hebt waarbij het form meerdere keren gevalideerd wordt zal deze bij de 2e ronde false geven op deze hash.

Hieronder een simpele setup voor dit element in je form:

$this->addElement('hash', 'csrf_hash', array(
    'salt' => 'justSomeRandomSalt',
    'timeout' => 600,
));

ps. de documentatie op de Zend Framework website is niet erg diepgaand op dit vlak. Als je in de broncode van de Form/Element/Hash.php kijkt zie je dat er meer parameters zijn die je mee kunt geven.

  • setSalt($salt)
    String met salt. Geef je niets mee dan gebruikt hij standaard de string “salt”
  • setSession($session)
    Zend_Session_Namespace $session als je zelf een session wilt meegeven.
  • setTimeout($ttl)
    Int met timeout welke standaard op 300 staat.

Ongewenste gegevens

Hoe ver je hiermee gaat hangt een beetje van de aard van de data af, de plek waar het form zit (publiek of besloten) en of de data bijvoorbeeld naar een database gaat of alleen gemaild wordt naar een persoon. Tevens laat ik hierin de beveiliging van de opslag, verzending of wat dan ook van de data buiten wegen. Het gaat me nu even puur om de formulier-afhandeling.

Met Zend_Filter en Zend_Validator kun je krachtige combinaties maken welke de data strippen op alleen het nodige na en het resultaat hiervan valideren. Stel je hebt een select veld:


$this->addElement('select', 'is_lovingchocolate', array(
    'label' => 'Do you love chocolate?',
    'multiOptions' => array(
        1 => 'Yes',
        0 => 'No',
    ),
    'filters' => array('int'),
));

Wat je hiermee doet is in iedere geval gelijk de formulierwaarde die terugkomt met de POST typecasten naar INT.  Met de verschillende Zend_Filter classes kun je op die manier al heel veel ellende tegenhouden. Als je ingewikkeldere data-patronen hebt kun je ervoor kiezen met eenvoudige Zend_Filters alle ongewenste data eruit te halen om daarna met Zend_Validate_Regex de data tegen een hele strict patroon te matchen.

Een voorbeeld hiervan:


$this->addElement('text', 'zipcode', array(
'label' => 'Zipcode (Dutch notation)',
'required' => true,
'maxlength' => 7,
'validators' => array(
    array('NotEmpty', true),
    array(
        'regex', false, array('/^[0-9]{4} [A-Z]{2}$/',
        'messages' => array('regexNotMatch' => 'Incorrect zipcode notation. Should look like 2222 AA.'))
    ),
),
'filters' => array('StripTags', 'stringToUpper'),
));

Wat we doen met de filters is alles eruit halen wat we nu even niet willen en gelijk de letter naar hoofdletters converteren voor een uniforme dataopslag later. Na deze stap valideren we het resultaat met een reguliere expressie voor de postcode. Even los van het feit of deze expressie aansluit bij jouw idee van een goede postcode maar denk dat het idee wel helder is zo.

Form based authentication for websites

Op Stackoverflow is men druk bezig in een poging “The Definitive Guide To Forms based Website Authentication” te maken. Komen best wat interessante punten voorbij. Sommige zijn relatief eenvoudig implementeerbaar in bestaande omgevingen terwijl andere “aanbevelingen” wat dieper gaan. Zeker als je een nieuwe applicatie opzet die nét dat stukje meer veiligheid vereist kan dit een heel interessant document zijn om nog eens kritisch te kijken naar je bestaande authenticatieprocedures.

De highlites zoals ik ze zie

Onderstaand lijstje is absoluut niet als richtlijn te gebruiken maar meer als “o ja, die” reminders van de zaken die in iedere geval in het oog springen, te gemakkelijk zijn om niet te doen of een flink verschil maken in de veiligheid van de login:

  • CAPTCHA’s zijn in basis nutteloos (op reCAPTCHA na want die is echt onmogelijk irritant)
  • Ben je goeroe dan ga je voor een Diffie-Hellman key exchange (wiki)
  • Cookies voor je sessie geef je het beste een HTTP Only header mee en zijn ‘secure’
  • Mocht je een “onthoudt mij” willen sla dan in iedere geval een zeer gezouten hash op van de token in je DB en niet de ‘kale’ cookie token
  • Geheime vragen zijn evil en vormen een veiligheidslek
  • Wachtwoord vergeten = nieuwe kiezen. Een “gereset” wachtwoord toezenden wat onmogelijk ingewikkeld is wordt vermoedelijk geprint, op een post-it gezet etc.
  • Stel minimale eisen aan een wachtwoord (javascript wachtwoord complexiteit checker)
  • Blokkeer massale login pogingen door pogingen te loggen en blocks uit te delen bij X aantal false attempts
  • Geef straf “delays” bijv. 2 seconde na 3e foutieve login, 5 seconde na 4e foutieve login etc zodat het onmogelijk is massa’s logins te testen
  • Gebruik SSL (beetje open deur maar hij hoort erbij)
  • Sla wachtwoorden alleen encrypted en salted op

De bron

Dit zijn maar een paar korte hightlites uit dit prachtige artikel:
http://stackoverflow.com/questions/549/the-definitive-guide-to-forms-based-website-authentication