Sessions im Request für Shopsysteme

Sessions ohne Speicherung der Session-Daten (Cookies, Files, DB)

  • Das System
  • Vorteile - Nachteile
  • Codebeispiel für nicht-cookiebasierte Sessions
  • Original-Perlunity-Thread (kopiert, damit Quelle nicht verloren geht)

    Das System

  • Cookies

    Man geht davon aus, dass keine Cookies beim User möglich sind. ( Wenn das Gegenteil bewiesen ist, übergibt man die SID doch besser in einem Cookie und nicht im Request )

  • Verfallsdatum

    Die Session-ID braucht dringend ein Verfallsdatum. (z.B. 6 Stunden) (Bspl.: Die Kunden eines Shopsystems verlinken einzelne Produktseiten, samt ihrer ID...) Wenn eine ID verfallen ist vergibt man stillschweigend eine neue. Wenn eine falsche ID eingegeben wurde ( mit ner Regex das Schema prüfen ) vergibt man stillschweigend eine neue.

  • Kontra Abspeichern der Session

    Auf keinen Fall, macht man für jede Session einen Datenbankeintrag. Diese Einträge braucht man erst, wenn der Warenkorb befüllt wird.

  • Der Warenkorb

    Warenkörbe haben einen extra Platz in so einer ID. Dieser Teil ist, wenn der Warenkorb befüllt wurde, immer gleich. Eigentlich ist das eine zweite ID innnerhalb der Session. Dieser Teil hat ein längeres Haltbarkeitsdatum (12 Wochen), wird aber, bei einer abgelaufenen Sid, nur akzeptiert, wenn er aus einen Cookie kommt.

  • Suchmaschinen und Spider

    Auf Suchmaschinen (Spider) muß man achten. Wenn die sich in so einen Shop verbeissen und Dank der ewig neuen Sid's, ewig neue Seiten finden kann das unschön enden. (Ich würde hier KFSW::SpiderDetect empfehlen, wenn ich es schon veröffentlicht hätte. SCNR) Ein erkannter Spider bekommt eine fixe ID, z.B. my_shop_name, dann findet er jede mögliche Seite nur einmal und gut ist. Da die Id nicht dem normalen Schema entspricht wird sie bei einem normalen User-Request ersetzt. (s.o) Dabei gibt es entweder eine neue ID oder, wenn der User schon einen Cookie haben sollte, die ID aus dem Cookie.

  • Sicherheit

    Alles Sicherheitsdenken, Kontrollierwünsche etc scheitern an der zu hohen Systemlast. Plane die Sids ohne irgendwelche sicherheitsrelevanten Features. (Für den Adminbereich mußt du natürlich was anderes machen.)

  • Beispiel Sid:
    perl -e '$sid = time() . "XY" . rand(1) . "ZZ" . time() . "XY" . rand(1) . "ZZ" . time();
    $sid =~ s/\./PT/g; 
    print"$sid\n"'
    
    Ausgabe:
    1050269667XY0PT917397911734046ZZ1050269667XY0PT0304125736906542ZZ1050269677
    

    Wenn du das jetzt immer an ZZ zerlegst hast du drei Teile. Warenkorb, eigentliche Sid und Cachebrecher. Jeder Teil hat sein eigenes Startdatum.

    Beim nächsten Aufruf wäre die Sid z.B.

    1050269667XY0PT917397911734046ZZ1050269667XY0PT0304125736906542ZZ1050269700
    

    Dieser String wird jetzt wie folgt geprüft: Erstmal eine RegEx drüber ob das Ding passen kann. Wenn das OK ist, geht es weiter:

    Codebeispiel:

    sub validate_session{
    # ---- usage
    # if( validate_session( $sessionDataToValidate ) ){ print "Session Ok!\n"; }
    # requires: constant SESSION_TIME = 123; #ms
    # ----
    # prüfen, ob gültige sid: a)muster b)haltbarkeit
       my $session = $_[0];
       my $return = 0;
       if( $session =~ /\d{10}XY\d*PT\d{10}/
       and (split /XY/, $session)[0] > time() - SESSION_TIME ){
               $return = 1;
       }
       return $return;
    }
    # --------------------------------------------------------
    

    a.) Wenn die Sid aus einem Cookie kommt, bleibt der erste Teil für z.B. sieben Wochen gültig. Der zweite Teil wird nach z.B. 6 Stunden erneuert. Der dritte Teil wird bei jedem Request durch time() ersetzt.

    b.) Wenn die Sid nicht aus einem Cookie kommt, gibt es nach 6 Stunden eine komplett neue Sid. Der dritte Teil wird bei jedem Request durch time() ersetzt.

  • Warum dieses System? Damit funktionieren Warenkörbe zwar nur mit Cookie richtig gut, aber ein Kunde, der in den Shop kommt und keine Cookies hat, kann dort in Ruhe einkaufen. (Er muß natürlich in 6 Stunden durch sein) Anders geht es leider nicht, denn wenn man die Warenkorbinformation aus dem Query-String länger als X Stunden akzeptiert, hat man wieder das Problem mit den Kunden, die Shopseiten verlinken und Ihre Sid im Link lassen. Gleiches gilt für versehentlich nicht erkannte WebSpider.

    Vorteile - Nachteile

    Vorteile

  • Es werden keine unnötigen Daten gespeichert
  • Funktioniert auch ohne Cookies (die jeder Benutzer deaktivieren kann)
  • Nachteile

  • Wenn der Benutzer keine Cookies annimmt kann er nur für einen begrenzten Zeitraum einkaufen
  • Spider raffen die Session nicht und finden so immer neue Seiten)
  • st nicht sicher genug - die Session ist immer einen bestimmten Zeitraum gültig. Kommt jemand anderes an die Session ran kann er sie ausnutzen

    Codebeispiel für nicht-cookiebasierte Sessions

    Code

    ...
    use constant SESSION_TIME =&> &'60000&'; #ms
    ...
    # ---- CGI
    my &$cgi = CGI-&>new&(&);
    my &$query = &$cgi-&>Vars&(&);
    # ---- SETTINGS
    my %subs = &(&);
    &$subs{relative_url} = &$cgi-&>url&(-relative=&>1&);
    &$subs{full_url}     = &$cgi-&>url&(-full=&>1&);
    ...
    print &$cgi-&>header&(-charset=&>&'ISO-8859-1&',
                      -expires=&>&'+1s&',
                      -type=&>&'text/html&',
                      &);

    if&( &(exists &$query-&>{sid}&)                                                      # wenn session existiert
    and validate_session&( &$query-&>{sid} &) &){                                          # und gültig ist
           #session verlängern
           &$query-&>{sid} = time&(&) . &"XY&" . &(split /XY/, &$query-&>{sid}&)&[1&];
           #settings
           &$subs{self} = &$subs{relative_url} . &'?sid=&' . &$query-&>{sid} . &'&file=&' . &$query-&>{file};
           &$subs{query} = &'?sid=&' . &$query-&>{sid} . &'&file=&' . &$query-&>{file};
           &$subs{domain} = &"http&://&" . DOMAIN;
           &$subs{sid} = &$query-&>{sid};
           &$subs{file} = &$query-&>{file};
    ...
    }else{ #wenn keine session existiert / session nicht gültig ist
           if&( exists &$query-&>{action}
           and &$query-&>{action} eq &"login&"
           and main&:&:validate_login&(&$query-&>{usn}, &$query-&>{pwd}&) &){
               &$query-&>{sid} = time&(&) . &"XY&" . rand&(1&);
               &$query-&>{sid} =~ s/&\./PT/g;
               #settings
               &$subs{self} = &$subs{relative_url} . &'?sid=&' . &$query-&>{sid} . &'&file=&' . &$query-&>{file};
               &$subs{query} = &'?sid=&' . &$query-&>{sid} . &'&file=&' . &$query-&>{file};
               &$subs{domain} = &"http&://&" . DOMAIN;
               # erstes einloggen =&> INDEX
               print qq~&Login korrekt&!&~;
               print qq~&&weiter >>>&&~;
               print qq~&SID&: &$query-&>{sid}&~;
               print qq~&file&: &$query-&>{file}&~;
           }else{
               print &$loginForm;
           }
    }
    exit&( 1 &);
    ...
    # --------------------------------------------------------
    # SUBS
    # --------------------------------------------------------
    sub validate_login{
    # ---- usage
    # if&( validate_login&( &$query-&>{usn}, &$query-&>{pwd} &) &){ print &"Login ok&!&\n&"; }
    # ---- requirements
    # modul&: Crypt&:&:PasswdMD5
    # &$passfile -&> File mit USN&|PWD&(cryptedBy&: Crypt&:&:PasswdMD5&)&\n
       my &(&$usn, &$pwd&) = @_;
       my &$return = 0;
       open&(DAT, &$passfile&) &|&| die &"&$&! &(&$passfile&)&";
       flock DAT, 1 if UNIX;
       my @passfile = &;
       close&(DAT&);
       foreach &( @passfile &){
           chomp &$_;
           if&( &$usn eq &(split /&\&|/,&$_&)&[0&] &){
               if &(unix_md5_crypt&(&$pwd, &(split /&\&|/,&$_&)&[1&]&)
                  eq &(split /&\&|/,&$_&)&[1&] &) {
                   # Passwort in Ordnung
                   &$return = 1;
               }else{
                   &$return = 0;
               }
           }else{
               &$return = 0;
           }
       }
       return &$return;
    }
    # --------------------------------------------------------
    sub validate_session{
    # ---- usage
    # if&( validate_session&( &$sessionDataToValidate &) &){ print &"Session Ok&!&\n&"; }
    # ----
    # prüfen, ob gültige sid&: a&)muster b&)haltbarkeit
       my &$session = &$_&[0&];
       my &$return = 0;
       if&( &$session =~ /&\d{10}XY&\d*PT&\d{10}/
       and &(split /XY/, &$session&)&[0&] &> time&(&) - SESSION_TIME &){
               &$return = 1;
       }
       return &$return;
    }
    # --------------------------------------------------------

    Original-Perlunity-Thread (kopiert, damit Quelle nicht verloren geht)

    Sessions für Shopsysteme
    Quelle: Threat auf perlunity.de

    Hi!
    Will mir gerade ein Session-Management-Teil stricken. Dazu wollte ich den User über die IP identifizieren (mehr dazu siehe weiter unten). Bei AOL-Usern ist das aber etwas doof, weil die ja jedesmal eine andere IP haben (weis nicht mehr genau wieso, war glaube ich wegen anonymen Proxy oder so - egal). Trifft das noch auf andere User-Gruppen zu?

    Zurück zur Frage, wo steht nochmal drin, ob der User AOL oder sonstwas benutzt?

    Dann noch was zum Session-Teil. Hatte eigentlich vor, auf Kekse soweit es geht zu verzichten. Jetzt steht allerdings Leistungsfähigkeit & Sicherheit gegen Kekse. Wieso sollte man nochmal auf den Einsatz von Keksen verzichten? (außer, dass manche Leute Kekse aus verständlichen Gründen deaktivieren) Was würdet ihr machen?

    --> Kurze Anmerkung, wie ich mir das vorstelle (in schmuckem pseudo-perl):

    if( AOL-User ){
    Keks mit SessionID setzen
    Eintrag in Session-DB, mit SessionID & "AOL"
    }else{
    Eintrag in Session-DB mit SessionID & IP
    #optional: Keks setzen
    }
    

    mfg pktm

    Hallo!
    Die IP ist generell keine gute Idee. Nebst AOL benutzen viele Provider Proxis, ob für den Kunden sichtbar oder nicht. Wenn du uns sagst, wofür du die Session brauchst und warum du keines der vorhandenen Module nutzen willst, ist es einfacher dir etwas zu raten. Ich mache je nach Bedarf mal Sessions mit Cookies oder im Querystring, mit TimeOut oder ohne, bei Cookies aber geneerell mit Fallback, für den Fall, daß der User keine Kekse will. Es kommt auf den Anwendungsbereich an. Ganz am Anfang habe ich mal, im Shop, für jeden Sch.... ne Session angelegt mit Kontrollfunktion etc. Ab 1000 Dateien pro Tag wird man da ruhiger;-)

    Gruß
    Kristian

    Das mit den 1000 Dateien kann ich glaube ich verstehen. Die Session wollte ich in mein Shopsystem einbauen. Einmal für den Admin-Bereich und einmal für den Warenkorb. Das mit AOL ist irgendwie was spezielles, weil die Leute jedesmal über eine anderen Proxy geschickt werden, mehr dazu gabs mal hier:

    http://perl.black-cube.net/bin/ultraboard/UltraBoard.cgi?action=Read&BID=13&TID=20114&P=1&SID=99#ID24

    aber der Link geht nicht mehr;-(

    Mit den anderen Proxys hab ich eigentlich doch kein Problem, bleibt doch immer der selbe - oder?

    Dann gibts da ja noch die Möglichkeit mit mod_rewrite, den hab ich aber bei meinem Provider nicht.

    Hm, also - Vorschläge? :-)
    mfg pktm

    Hallo!
    Vergiß die AOL-User und überdenke dein Session-Konzept. In einem Shop würde ich es so machen:

    Erstmal geht man davon aus, daß keine Cookies beim User möglich sind. ( Wenn das Gegenteil bewiesen ist, übergibt man die SID in einem Cookie und nicht im Request )

    Die Session-ID braucht dringend ein Verfallsdatum. (z.B. 6 Stunden) (Die Kunden verlinken einzelne Produktseiten, samt ihrer ID...) Wenn eine ID verfallen ist vergibt man stillschweigend eine neue. Wenn eine falsche ID eingegeben wurde ( mit ner Regex das Schema prüfen ) vergibt man stillschweigend eine neue.

    In der Session-ID "bricht" man, bei Bedarf, den Browsercache, indem man time() mit einbaut.

    Auf keinen Fall, macht man für jede Session einen Datenbankeintrag. Diese Einträge braucht man erst, wenn der Warenkorb befüllt wird.

    Warenkörbe haben einen extra Platz in so einer ID. Dieser Teil ist, wenn der Warenkorb befüllt wurde, immer gleich. Eigentlich ist das eine zweite ID innnerhalb der Session. Dieser Teil hat ein längeres Haltbarkeitsdatum (12 Wochen), wird aber, bei einer abgelaufenen Sid, nur akzeptiert, wenn er aus einen Cookie kommt.

    Auf Suchmaschinen (Spider) muß man achten. Wenn die sich in so einen Shop verbeissen und Dank der ewig neuen Sid's, ewig neue Seiten finden kann das unschön enden. (Ich würde hier KFSW::SpiderDetect empfehlen, wenn ich es schon veröffentlicht hätte. SCNR) Ein erkannter Spider bekommt eine fixe ID, z.B. my_shop_name, dann findet er jede mögliche Seite nur einmal und gut ist. Da die Id nicht dem normalen Schema entspricht wird sie bei einem normalen User-Request ersetzt. (s.o) Dabei gibt es entweder eine neue ID oder, wenn der User schon einen Cookie haben sollte, die ID aus dem Cookie.

    Alles Sicherheitsdenken, Kontrollierwünsche etc scheitern an der zu hohen Systemlast. Plane die Sids ohne irgendwelche sicherheitsrelevanten Features. (Für den Adminbereich mußt du natürlich was anderes machen.)

    So, da hast du ein paar Anregungen, die auf drei Jahren (Shopbetrieb)Erfahrung beruhen, ich hoffe sie helfen dir.

    Gruß
    Kristian

    Hi!
    Das Konzept leuchtet ein´: * Cookie immer setzen, wenn möglich * SID in request mitgeben Nur das mit dem Warenkorb hab ich nicht so ganz verstanden. Die SID besteht aus 2 Teilen.
    1. Teil: localtime + 6h, irgendwie verdreht
    2. Teil: ? Warenkorb ?
    Einfach eine zufallsgenerierte ID nehmen? Geht der Teil denn nicht verkoren, wenn ich stillschweigend eine neue ID vergebe?

    mfg pktm

    Hallo
    > Das Konzept leuchtet ein´:
    > * Cookie immer setzen, wenn möglich
    > * SID in request mitgeben
    Hmm, nein, da habe ich gefaselt, einen Cookie gibt es nur, wenn er was in den Warenkorb legt.

    Beispiel Sid:

    perl -e '$sid = time() . "XY" . rand(1) . "ZZ" . time() . "XY" . rand(1) . "ZZ" . time();
    $sid =~ s/\./PT/g; 
    print"$sid\n"'
    
    Ausgabe:
    1050269667XY0PT917397911734046ZZ1050269667XY0PT0304125736906542ZZ1050269677
    

    Wenn du das jetzt immer an ZZ zerlegst hast du drei Teile. Warenkorb, eigentliche Sid und Cachebrecher. Jeder Teil hat sein eigenes Startdatum.

    Beim nächsten Aufruf wäre die Sid z.B.

    1050269667XY0PT917397911734046ZZ1050269667XY0PT0304125736906542ZZ1050269700
    

    Dieser String wird jetzt wie folgt geprüft: Erstmal ne regex drüber ob das Ding passen kann. Wenn das OK ist, geht es weiter:

    a.) Wenn die Sid aus einem Cookie kommt, bleibt der erste Teil für z.B. sieben Wochen gültig.
    Der zweite Teil wird nach z.B. 6 Stunden erneuert.
    Der dritte Teil wird bei jedem Request durch time() ersetzt.

    b.) Wenn die Sid nicht aus einem Cookie kommt, gibt es nach 6 Stunden eine komplett neue Sid.
    Der dritte Teil wird bei jedem Request durch time() ersetzt.

    Damit funktionieren Warenkörbe zwar nur mit Cookie richtig gut, aber ein Kunde, der in den Shop kommt und keine Cookies hat, kann dort in Ruhe einkaufen. (Er muß natürlich in 6 Stunden durch sein) Anders geht es leider nicht, denn wenn man die Warenkorbinformation aus dem Query-String länger als X Stunden akzeptiert, hat man wieder das Problem mit den Kunden, die Shopseiten verlinken und Ihre Sid im Link lassen. Gleiches gilt für versehentlich nicht erkannte WebSpider.

    Sind damit alle Klarheiten restlos beseitigt?;-)

    Gruß Kristian

    PS: 6 Stunden / 7 Wochen kann man natürlich durch andere Werte ersetzen

    Hi,
    denke mal, dass bei AOL für die IP auch ein hostname mit "AOL" existiert. So wie bei t-online oder netcologne:

    >>dial-213-168-xx-xxx.netcologne.de<<
    

    so kommst du an den hostnamen ran:

    #!/usr/bin/perl -w
    
    use strict;
    use CGI::Carp qw(fatalsToBrowser);
    
    print "Content-type: text/html\n\n";
    print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">', "\n";
    print "<html><head><title>Host-Ausgabe der REMOTE-Adresse</title></head><body><pre>\n";
    
    use Socket;
    my $remote=$ENV{'REMOTE_ADDR'};
    my $addr = inet_aton($remote);
    my $Wert = gethostbyaddr($addr, AF_INET);
    print "$Wert\n";
    print "</pre></body></html>\n";
    

    gruß klaus

    Ende