Perl en CGI scripts 2

Dit is een vervolg-pagina op deel 1!

Op deze pagina wat meer gevorderde onderwerpen: hoe krijg je informatie van een aanroep van je script naar het volgende (op der server is iedere aanroep een nieuwe), en wat is er nodig voor zaken als beveiliging?

State overdracht: het probleem

Een probleem met cgi-scripts is dat iedere aanroep als een nieuwe aanroep wordt gezien (het is een 'stateless' protocol). Heb je een CGI script waar je in meerdere aanroepen doorheen loopt (b.v. eerst inloggen, dan een keuze maken, dan daar iets mee doen; en dit iedere keer bijvoorbeeld via een nieuwe submit in een form), dan weet iedere aanroep niets van de gegevens van de vorige aanroep af. Bovendien: er kunnen best meerdere gebruikers tegelijk actief zijn: hoe zou het programma moeten weten welke aanroep bij welke gebruiker hoort?

Het probleem is te splitsen in twee varianten:

  • De versie waarbij je op korte termijn gegevens van de ene aanroep naar de volgende wilt overdragen (typisch binnen een sessie)
  • De versie waarbij je op de computer van de gebruiker gegevens wilt achterlaten om daar een volgende keer, mogelijk vele dagen later, mee verder te kunnen: dit wordt normaal met een cookie gedaan (zie verderop).

State overdracht tussen opeenvolgende aanroepen

Hier zijn verschillende methodes voor. Wat ik zelf doe (bij beperkte hoeveelheid state), is deze meegeven aan de pagina die wordt weergegeven in reactie op het aanroepen van de cgi door de gebruiker. Op deze nieuwe pagina komt ook vast minimaal een form voor (uiteindelijk wil je neem ik aan verder met waar de gebruiker is gebleven, en kan deze weer volgende stappen maken). Wat ik dan doe is in deze form 'hidden fields' opnemen, en hier als waarde de vorige state meegeven. Dit heeft als voordeel dat je deze net als nieuwe waarden gewoon met param() kunt teruglezen (zie vorige pagina over cgi). De form van deze vorige pagina zou er dan bijvoorbeeld als volgt uit kunnen zien:

  <form action="http://www.je_web_site.nl/cgi/argument.cgi" method="post">
    <p>De parameter:
    <input size="40" maxlength="100" name="argument" type="text">
    <input type="hidden" name="oldstate" value="de oude waardes">
    <input value="Verzend!" type="submit"></p>
  </form>

Hier is een input met type hidden bijgevoegd (door het cgi script), met de waarde die we door willen geven. De gebruiker ziet dit verder niet (en kan het ook niet wijzigen). De waarde is met param('oldstate') op te halen in het vervolgens aangeroepen cgi script. Je kan eventueel meerdere inputs gebruiken om meerdere waarden mee te geven (of je combineert ze in een string). Je kunt eventueel de waarde nog encrypten, als je niet wilt dat de gebruiker ze ziet (en wijzigt...). Zelf stop ik er ook wel de huidige tijd in; en geef een time-out als de pagina te lang niet wordt opgestuurd.

Een alternatief (bij erg veel state) is de state op de server opslaan in een database of file, en alleen een session ID (een of ander volgnummer) meegeven in een hidden input field. Het cgi script kan nu via deze session ID weten welke state er bij hoort (ook handig als de gebruiker bijvoorbeeld meerdere pagina's open zou kunnen hebben, waarbij de state tussen de pagina's inconsistent kan raken als je deze direct in de hidden fields van de diverse pagina's hebt staan). Wel moet je dan nu en dan de database opschonen met state van sessies die door de gebruiker al niet meer in gebruik zijn (bijvoorbeeld meer dan een dag oud).

Cookies explained (Koekjes uitgelegd)

Cookies zijn kleine pakketjes, die door de server naar je browsers worden verstuurd, en daar bewaard zullen blijven. Ze kunnen vervolgens weer door de browser worden opgehaald. Ze zijn heel handig om data te bewaren tussen sessies; zodat je een volgende keer verder kunt waar je gebleven was. Ook voor het bewaren van wachtwoorden en dergelijke zijn ze handig, zodat je niet iedere keer opnieuw hoeft in te loggen.

De naam is gebaseerd op de Chinese gelukskoekjes, van die knapperige gevouwen rondjes met een briefje met gelukswens er in. Cookies zijn niet per se fout, maar worden ook nogal eens gebruikt om internetgedrag te volgen, en worden daardoor gewantrouwd. Hierdoor heeft niet iedereen cookies toegelaten; je kan dus niet 100% op de werking vertrouwen!

Het lezen van een cookie gaat heel eenvoudig. Elke cookie heeft een naam. De waarde van cookies horende bij je set met web-pages (afhankelijk van domeinnaam en directory) worden door de browser automatisch opgehaald, en met de cookie() functie kan je die vervolgens lezen. In mijn voorbeeld-cgi 'meer_data.cgi' (zie deel 1, probeer, of download.) wordt bijvoorbeeld een koekje met de naam 'meer_data' gezet met de tijd van de vorige aanroep, deze is dan weer te lezen door middel van de aanroep cookie('meer_data'). De functie geeft een ongedefinieerde waarde terug als er geen koekje met die naam aanwezig is. Ps: lees het oude cookie voor je een nieuwe aanmaakt, zie het voorbeeld hieronder.

Het zetten van een simpel cookie is niet veel moeilijker; je maakt een nieuw cookie voordat je de nieuwe pagina genereert, en je geeft de nieuwe cookie mee aan de header() aanroep. Het volgende stukje geeft het betreffende stukje code uit meer_data.cgi:

my $oldcookie = cookie('meer_data') || 'No cookie set';

my $now = gmtime(time());           # current GMT time
my $newcookie = cookie(-name => 'meer_data',
                       -value => $now,
#                      -expires => '+24h',    # vervaldatum
                       -path => '/cgi');

print header(-cookie=>$newcookie);  # create the HTTP header, with new cookie

Meer mogelijkheden staan uitgelegd in de documentatie van van de CGI.pm module. Denk er wel aan: cookies zijn gewoon tekstuele informatie, en worden ook zo opgeslagen en over internet verstuurd (behalve bij beveiligde verbindingen); gebruik ze dus niet voor gevoelige (privé) gegevens.

Nog even een paar tips:

  • Zet niet te veel cookies; een per script is voldoende!
  • Houd ze ook beperkt in grootte, ze moeten wel iedere keer overgestuurd worden, en dat kost tijd
  • Geef ze een beperkte houdbaarheidsdatum
  • Denk aan (het gebrek aan) veiligheid (je kunt evt de inhoud zelf encrypten)

Meer informatie? Zoek eens op Google naar 'Perl CGI cookies', of kijk naar onderstaande links:

Security en de -T optie

Pagina in aanmaakDe -T optie (Taint checking mode)

De -T optie op de eerste regel van een cgi script (#!/usr/bin/perl -T) is een belangrijke: altijd zetten. Precieze uitleg volgt nog (of kijk de documentatie van 'secure Perl': de perlsec page). Maar kort gezegd komt het er op neer dat alles wat via het internet komt per definitie onbetrouwbaar is (iedereen kan zelf een cgi-string met de hand maken en naar je script toesturen, buiten je controle om). Stel je wilt een file openen waarvan de naam door een gebruiker is gegeven, wat als de gebruiker dat de volgende string meegeeft: '| rm *.*' ? Op bijvoorbeeld Unix (Linux) wordt dit opgevat niet als een filenaam, maar als een commando om de output door te sturen (pipen) naar het programma 'rm *.*' (remove alles), dat vervolgens je files wist...

Om dit te voorkomen worden alle gegevens van buiten als 'tainted' gemerkt (vies, bedorven), en met de -T optie weigeren alle gevaarlijke commando's tainted data (en kan je ook niet vergeten iets te checken). Uiteraard zijn er ook weer mogelijkheden om per item aan te geven dat je het netjes gefilterd hebt en het dus vanaf nu te vertrouwen is; als je zeker weet wat je doet. Zie ook weer de perlsec page.

Out-of-memory

Voorkomen van out-of-memory attacks (leukerds die je opeens 100 Mbyte willen sturen om je systeem te laten crashen): zie de documentatie van de CGI.pm module voor de volgende instellingen (neem statements op direct na het 'use CGI' statement):

$CGI::POST_MAX=1024 * 100; # max 100K size posts
$CGI::DISABLE_UPLOADS = 1; # no uploads allowed

Meer informatie