Jonas: Cookies werden nicht korrekt gesetzt

Hallo werte Programmierergemeinde,

da ich nun seit ca. 12 Stunden an einem Problem bzgl. Cookies sitze und einfach keine Lösung finden kann, springe ich hiermit über meinen Schatten und suche Rat :)

Es geht um eine Webapplikation zur Erstellung personalisierter Startseiten, welche auf PHP und einer Menge JS basiert. Der Grundgedanke der Implementierung basiert auf objektorientierter Programmierung in PHP: Eine personalisierte Seite wird durch eine Instanz der Page-Klasse repräsentiert, diese wiederum enthält Instanzen einer Widget-Klasse, welche ein Element der personalisierten Seite darstellt. Um dem User eine Login-Prozedur zu ersparen, speichere ich den entsprechenden Status der individuellen Seite, also das Page-Objekt, in einem Cookie. Ich hoffe, dass diese Infos vorerst reichen. Nun zu meinem Problem:
Betritt ein User die Seite, existiert entweder ein COOKIE oder auch nicht und es wird eins angelegt. Das sollte bzgl. der Lösungssuche keinen Unterschied machen, dennoch der Code:

	  
if(!isset($_COOKIE[COOKIE_NAME]) && empty($_COOKIE[COOKIE_NAME])) {  
		/* JUST SAMPLE CODE FOR DEFAULT PAGE */  
		$page = new Page('Default');  
		$page->addRSS('http://welt.de/wirtschaft/?service=Rss');  
		$widgets = $page->getWidgetList();  
		setcookie(COOKIE_NAME, base64_encode(serialize($page)), getCookieExpDate(), '/');  
	} else {  
		$page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));  
		$widgets = $page->getWidgetList();  
	}  

Per XMLHttpRequest wird jedes vorhandene Widget (in diesem Fall nur ein RSS Feed) aktualisiert. Hierzu wird ein Request auf die Datei 'pageController.php' ausgeführt. Über entsprechende POST-Daten entscheidet diese, was zu tun ist und landet im Fall des Refresh-Befehls in folgendem Block:

  
case 'refreshWidget':  
        if(isset($_COOKIE[COOKIE_NAME], $_POST['ID'])) {  
	$page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));  
	$widget = $page->getWidget($_POST['ID']);  
					  
	     if($widget->refreshContent()) {  
		setCookie(COOKIE_NAME, '', time()-42000, '/');  
		setcookie(COOKIE_NAME, base64_encode(serialize($page)), getCookieExpDate(), '/');  
                $widget->showContent(1);					  
	     } else {  
		$widget->showContent(1);  
	     }  
	}  
	break;  

Genau hier liegt das Problem. Das 'alte' Cookie, welches aus der vorhergehenden Seite stammt, soll gelöscht, bzw. überschrieben werden. Dies ist notwendig, da auf $widget refreshContent() angewendet wurde, die Inhalte dieses Widgets also aktualisiert wurden. Auf diese Weise kann ich ältere Daten cachen und falls die Ressource mal nicht verfügbar sein sollte, dem User zumindest alte Daten bereitstellen.
Führe ich den Code in dieser Form aus, existiert nach dem refreshContent() kein Cookie. D.h. das Cookie wird gelöscht, das neue aber nicht geschrieben. Wende ich setcookie an, ohne das Cookie vorher zu löschen, steht im Nachhinein das alte Cookie geschrieben, d.h. es wird nicht aktualisiert.

Findet ihr ein Problem in diesem Code?

Vielen Dank im voraus, beste Grüße,
Jonas

  1. Hi,

    if(!isset($_COOKIE[COOKIE_NAME]) && empty($_COOKIE[COOKIE_NAME])) {

    Was versprichst du dir von dieser Abfrage?

    Welchen Wert hat die Konstante COOKIE_NAME?

         if($widget->refreshContent()) {  
    
      setCookie(COOKIE_NAME, '', time()-42000, '/');  
      setcookie(COOKIE_NAME, base64_encode(serialize($page)), getCookieExpDate(), '/');  
    

    $widget->showContent(1);
         } else {
    $widget->showContent(1);
         }

      
    
    > Genau hier liegt das Problem. Das 'alte' Cookie, welches aus der vorhergehenden Seite stammt, soll gelöscht, bzw. überschrieben werden.  
      
    Warum versuchst du den Cookie überhaupt zu löschen, wenn du ihn doch anschließend gleich mit neuem Wert wieder zu setzen versuchst?  
    Warum setzt du nicht gleich nur den neuen Wert?  
      
    Und warum notierst du den Aufruf von showContent redundant im if- und im else-Zweig - anstatt bedingungsunabhängig \*nach\* der Abfrage?  
      
    
    > Dies ist notwendig, da auf $widget refreshContent() angewendet wurde, die Inhalte dieses Widgets also aktualisiert wurden.  
      
    Und was macht diese Methode, was für einen Rückgabewert liefert sie?  
      
    
    > Führe ich den Code in dieser Form aus, existiert nach dem refreshContent() kein Cookie. D.h. das Cookie wird gelöscht, das neue aber nicht geschrieben. Wende ich setcookie an, ohne das Cookie vorher zu löschen, steht im Nachhinein das alte Cookie geschrieben, d.h. es wird nicht aktualisiert.  
      
    Hast du clientseitig analysiert, welche Cookies betreffenden HTTP-Header ankommen?  
      
    Beobachtest du das Problem nur in einem bestimmten Testbrowser, oder übergreifen?  
    Hast du die Nutzerkommentare im Manual zu setcookie durchgeschaut, welche Fallstricke es ggf. zu beachten gilt?  
      
    
    > Findet ihr ein Problem in diesem Code?  
      
    Einige, aber weniger mit direktem Bezug auf das Problem.  
      
    MfG ChrisB  
      
    
    -- 
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    
    1. Hey, erstmal besten Dank für deine Antwort!

      if(!isset($_COOKIE[COOKIE_NAME]) && empty($_COOKIE[COOKIE_NAME])) {

      Was versprichst du dir von dieser Abfrage?

      Welchen Wert hat die Konstante COOKIE_NAME?

      User Contributed Notes: php.net. Wie du siehst ist COOKIE_NAME ('PREF') eine Konstante, ist also völlig irrelevant was diese enthält.

      Warum versuchst du den Cookie überhaupt zu löschen, wenn du ihn doch anschließend gleich mit neuem Wert wieder zu setzen versuchst?
      Warum setzt du nicht gleich nur den neuen Wert?

      Weil ich auf Lösungssuche bin und verschiedene Varianten teste. Wie beschrieben funktioniert es auch nicht, ohne das Cookie zuvor zu löschen und einfach neu zu setzen.

      Und warum notierst du den Aufruf von showContent redundant im if- und im else-Zweig - anstatt bedingungsunabhängig *nach* der Abfrage?

      Ich habe den Code sehr stark reduziert, um das Problem einzugrenzen. Dabei ist obenstehender Code übrig geblieben. In diesem Context macht es tatsächlich keinen Sinn, ist für die Lösungssuche aber auch nicht relevant.

      Dies ist notwendig, da auf $widget refreshContent() angewendet wurde, die Inhalte dieses Widgets also aktualisiert wurden.

      Und was macht diese Methode, was für einen Rückgabewert liefert sie?

      refreshContent aktualisiert lediglich die Daten innerhalb des Widget-Objekts, in diesem Beispiel (RSS Widget) wird ein Daten Array aktualisiert. Das funktioniert tadellos. Dazu folgendes Beispiel:

        
      if($widget->refreshContent()) {  
      	if(setcookie(COOKIE_NAME, base64_encode(serialize($page)), getCookieExpDate(), '/')) echo "<h2>SUCCESS</h2>\n";  
      	var_dump($widget->items);  
      	$page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));  
      	$widget = $page->getWidget($_POST['ID']);  
      	echo "<br /><br />\n";  
      	var_dump($widget->items);					  
      }  
      
      

      Der erste Aufruf von var_dump($page); zeigt, dass die Daten innerhalb des Widget-Objektes erfolgreich aktualisiert wurden. Ich serialisiere das Page-Objekt, welches also definitiv das aktualisierte widget-Objekt enthält und schreibe es in das Cookie. Anschließend lese ich das Cookie, deserialisiere das Page-Objekt und der zweite Aufruf von var_dump($page) zeigt, dass die aktualisierten Daten nicht enthalten sind. Daraus schließe ich, dass die Objekte korrekt funktionieren, nur das Cookie nicht geschrieben wurde. Was allerdings auch nicht der Fall sein kann, da die Ausgabe von "<h2>SUCCSS</h2>" bei erfolgreichem Schreiben des Cookies erfolgt.

      Führe ich den Code in dieser Form aus, existiert nach dem refreshContent() kein Cookie. D.h. das Cookie wird gelöscht, das neue aber nicht geschrieben. Wende ich setcookie an, ohne das Cookie vorher zu löschen, steht im Nachhinein das alte Cookie geschrieben, d.h. es wird nicht aktualisiert.

      Hast du clientseitig analysiert, welche Cookies betreffenden HTTP-Header ankommen?

      Bislang nur sporadisch per Firebug. Ich habe hier keine weitere Analyse unternommen, da das Ergebnis mit dem der serverseitigen Verarbeitung übereinstimmt. Das Cookie wird nicht aktualisiert, der Inhalt bleibt der gleiche. Dabei habe ich das Cookie aus dem Header mit dem Wert des Cookies auf der Serverseite verglichen.

      Beobachtest du das Problem nur in einem bestimmten Testbrowser, oder übergreifen?

      Übergreifend, bislang: FF4, IE8, Chrome

      Hast du die Nutzerkommentare im Manual zu setcookie durchgeschaut, welche Fallstricke es ggf. zu beachten gilt?

      Ja, und genau deshalb habe ich die verschiedensten Varianten ausprobiert (z.B. Cookie vorher löschen, direkt $_COOKIE zu beschreiben, Cookie erneut zu setzen).

      Findet ihr ein Problem in diesem Code?

      Einige, aber weniger mit direktem Bezug auf das Problem.

      Wie gesagt bin ich seit ca. 12 Stunden auf Fehlersuche, der Code hat sich während dieser Zeit stark vermüllt. Ich denke ich weiß was es heißt sauber zu programmieren, bitte drückt hier mal kurz ein Auge zu, auch wenn es schwer fällt :)

      Beste Grüße,
      Jonas

      1. Hi,

        Welchen Wert hat die Konstante COOKIE_NAME?

        Wie du siehst ist COOKIE_NAME ('PREF') eine Konstante, ist also völlig irrelevant was diese enthält.

        Ob das wirklich eine Konstante ist, oder nur schludriger Umgang mit der Syntax, wollte ich herausfinden.

        if($widget->refreshContent()) {

        if(setcookie(COOKIE_NAME, base64_encode(serialize($page)), getCookieExpDate(), '/')) echo "<h2>SUCCESS</h2>\n";
        var_dump($widget->items);
        $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));
        $widget = $page->getWidget($_POST['ID']);
        echo "<br /><br />\n";
        var_dump($widget->items);
        }

        
        >   
        > Der erste Aufruf von var\_dump($page); zeigt, dass die Daten innerhalb des Widget-Objektes erfolgreich aktualisiert wurden. Ich serialisiere das Page-Objekt, welches also definitiv das aktualisierte widget-Objekt enthält und schreibe es in das Cookie. Anschließend lese ich das Cookie, deserialisiere das Page-Objekt und der zweite Aufruf von var\_dump($page) zeigt, dass die aktualisierten Daten nicht enthalten sind. Daraus schließe ich, dass die Objekte korrekt funktionieren, nur das Cookie nicht geschrieben wurde.  
          
        Da schließt du falsch.  
        $\_COOKIE enthält die Cookie-Daten, die beim Aufruf des Scriptes über HTTP übermittelt wurden.  
        $\_COOKIE wird \*nicht\* aktualisiert dadurch, dass du mit setcookie einen neuen Cookie setzt. Diese Änderung macht sich in $\_COOKIE erst beim nächsten Scriptaufruf bemerkbar.  
          
        
        > Was allerdings auch nicht der Fall sein kann, da die Ausgabe von "<h2>SUCCSS</h2>" bei erfolgreichem Schreiben des Cookies erfolgt.  
          
        Der Rückgabewert von setcookie sagt absolut nichts darüber aus, ob der Cookie auch beim Client angekommen ist oder von diesem akzeptiert wurde.  
        Er sagt lediglich aus, dass das Hinzufügen eines neuen HTTP-Headers zum Response erst mal nicht fehlerhaft verlief.  
          
        
        > > Hast du clientseitig analysiert, welche Cookies betreffenden HTTP-Header ankommen?  
        >   
        > Bislang nur sporadisch per Firebug. Ich habe hier keine weitere Analyse unternommen, da das Ergebnis mit dem der serverseitigen Verarbeitung übereinstimmt.  
          
        Da deine Analyse bzw. Interpretation dieser aber bereits fehlerhaft war, solltest du noch mal ein bisschen genauer testen.  
          
        MfG ChrisB  
          
        
        -- 
        RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
        
        1. Hi,

          Da schließt du falsch.
          $_COOKIE enthält die Cookie-Daten, die beim Aufruf des Scriptes über HTTP übermittelt wurden.
          $_COOKIE wird *nicht* aktualisiert dadurch, dass du mit setcookie einen neuen Cookie setzt. Diese Änderung macht sich in $_COOKIE erst beim nächsten Scriptaufruf bemerkbar.

          Das wusste ich bislang noch nicht. Es ändert zwar nichts, da ich bereits den Fall getestet habe, ob das Cookie bei erneutem Skriptaufruf aktualisiert wurde, ist aber wichtig zu wissen!

          Da deine Analyse bzw. Interpretation dieser aber bereits fehlerhaft war, solltest du noch mal ein bisschen genauer testen.

          Das werde ich mal tun, danke dir!

          MfG ChrisB

        2. Hey,

          Da deine Analyse bzw. Interpretation dieser aber bereits fehlerhaft war, solltest du noch mal ein bisschen genauer testen.

          Abgesehen von der Kodierung der Daten:
          Die Cookies sollten, meiner Meinung nach, eigentlich korrekt gesetzt werden. Der Anfrage-Header enthält das "alte" Cookie, der Antwort-Header enthält Set-Cookie mit neuen Daten. Dennoch wird es nicht gesetzt.
          Reicht die Header-Anweisung Set-Cookie denn aus, um das Cookie auch wirklich zu schreiben oder muss die Seite, an die das Cookie übertragen wird, hierfür neu geladen werden?

          Gruß,
          Jonas

          1. Hi,

            Die Cookies sollten, meiner Meinung nach, eigentlich korrekt gesetzt werden. Der Anfrage-Header enthält das "alte" Cookie, der Antwort-Header enthält Set-Cookie mit neuen Daten. Dennoch wird es nicht gesetzt.

            Wie genau hast du das überprüft?

            Stimmen die Angaben zum „Gültigkeitsbereich“ des Cookies, also Domain und Pfad?
            Ist die Zeitangabe für den neuen Cookie korrekt, auch in Bezug auf die Client-Zeit, die ggf. von der Serverzeit abweichen kann?

            Reicht die Header-Anweisung Set-Cookie denn aus, um das Cookie auch wirklich zu schreiben oder muss die Seite, an die das Cookie übertragen wird, hierfür neu geladen werden?

            Du musst einen HTTP-Request ausführen, damit ein neuer Cookie gesetzt werden kann - ob dessen Ergebnis eine „Seite“ oder irgendetwas anderes ist (Bild, Script, ...), ist dabei ohne Belang.

            MfG ChrisB

            --
            RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
            1. Hi,

              Die Cookies sollten, meiner Meinung nach, eigentlich korrekt gesetzt werden. Der Anfrage-Header enthält das "alte" Cookie, der Antwort-Header enthält Set-Cookie mit neuen Daten. Dennoch wird es nicht gesetzt.

              Wie genau hast du das überprüft?

              Die Daten stammen aus Firebug.

              Stimmen die Angaben zum „Gültigkeitsbereich“ des Cookies, also Domain und Pfad?
              Ist die Zeitangabe für den neuen Cookie korrekt, auch in Bezug auf die Client-Zeit, die ggf. von der Serverzeit abweichen kann?

              Der Pfad stimmt überein: In beiden Fällen '/', für die Domain gültig. Die Zeit des Antwort-Headers weicht um genau 2 Stunden von der Client-Zeit ab.

              Du musst einen HTTP-Request ausführen, damit ein neuer Cookie gesetzt werden kann - ob dessen Ergebnis eine „Seite“ oder irgendetwas anderes ist (Bild, Script, ...), ist dabei ohne Belang.

              Wie gesagt wird ein HTTP-Request mittels Ajax auf den page controller ausgeführt. Dieser setzt das Cookie mit aktualisierten Daten. Dabei wird die Seite, von der der Request ausging, nicht neu geladen. Das sollte aber auch nicht erforderlich sein.
              Angenommen ich starte zwei Requests nacheinander. So dürfte bei dem ersten Request auf den page controller nur das alte Cookie zur Verfügung stehen, beim Zweiten allerdings bereits das aktualisierte, da beim Ersten das Cookie hätte geschrieben werden müssen, richtig?

              Gruß,
              Jonas

              1. Hi,

                Der Anfrage-Header enthält das "alte" Cookie, der Antwort-Header enthält Set-Cookie mit neuen Daten. Dennoch wird es nicht gesetzt.

                Wie genau hast du das überprüft?

                Die Daten stammen aus Firebug.

                Ich meinte vor allem die Feststellung, ob der Cookie „gesetzt“ ist oder nicht.

                Wie sieht es denn beim nächsten Request aus, welche Cookie-Daten enthält der Request-Header dabei?

                Der Pfad stimmt überein: In beiden Fällen '/', für die Domain gültig.

                Und „andere“ Cookies mit gleichem Namen, aber anderem Gültigkeitsbereich sind definitiv nicht im Spiel, auch nicht aus vorherigen Tests o.ä.?

                Die Zeit des Antwort-Headers weicht um genau 2 Stunden von der Client-Zeit ab.

                Damit meinst du hoffentlich nur die *Darstellung* der Zeitangabe, nicht den Zeitstempel an sich.

                Angenommen ich starte zwei Requests nacheinander. So dürfte bei dem ersten Request auf den page controller nur das alte Cookie zur Verfügung stehen, beim Zweiten allerdings bereits das aktualisierte, da beim Ersten das Cookie hätte geschrieben werden müssen, richtig?

                Theoretisch ja.

                MfG ChrisB

                --
                RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
                1. Hi,

                  Der Anfrage-Header enthält das "alte" Cookie, der Antwort-Header enthält Set-Cookie mit neuen Daten. Dennoch wird es nicht gesetzt.

                  Wie genau hast du das überprüft?

                  Die Daten stammen aus Firebug.

                  Ich meinte vor allem die Feststellung, ob der Cookie „gesetzt“ ist oder nicht.

                  Das schaue ich in den Browser Settings nach. Das dort enthaltene Cookie ist immer exakt das Gleiche. Der Zeitstempel verändert sich nicht, die Daten, der Pfad, die Domain ist identisch. Es ändert sich einfach definitiv nicht, d.h. das neue Cookie wird definitiv nicht gesetzt. Woran kann das liegen? Wie gesagt, es liegt nicht am Browser.

                  Wie sieht es denn beim nächsten Request aus, welche Cookie-Daten enthält der Request-Header dabei?

                  Der nächste Request-Header enthält wieder das ursprüngliche Cookie und gibt als Antwort wieder Set-Cookie mit neuen Daten zurück.

                  Der Pfad stimmt überein: In beiden Fällen '/', für die Domain gültig.

                  Und „andere“ Cookies mit gleichem Namen, aber anderem Gültigkeitsbereich sind definitiv nicht im Spiel, auch nicht aus vorherigen Tests o.ä.?

                  Nein, definitiv nicht.

                  Gruß,
                  Jonas

              2. Angenommen ich starte zwei Requests nacheinander. So dürfte bei dem ersten Request auf den page controller nur das alte Cookie zur Verfügung stehen, beim Zweiten allerdings bereits das aktualisierte, da beim Ersten das Cookie hätte geschrieben werden müssen, richtig?

                Zumindest ist dies nicht der Fall, der zweite Request enthält wieder das ursprüngliche Cookie... Und zwar exakt dieses.
                Wie finde ich heraus, ob mein Client das Cookie akzeptiert bzw. das warum, falls er es verweigert?

                1. Hi,

                  Wie finde ich heraus, ob mein Client das Cookie akzeptiert bzw. das warum, falls er es verweigert?

                  Firecookie integriert sich in Firebug, und bietet dir zahlreiche Informationen über die Cookies.

                  MfG ChrisB

                  --
                  RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
                  1. Hi,

                    Wie finde ich heraus, ob mein Client das Cookie akzeptiert bzw. das warum, falls er es verweigert?

                    Firecookie integriert sich in Firebug, und bietet dir zahlreiche Informationen über die Cookies.

                    Ok, in Firecookie wird nur das anfangs gesetzte Cookie angezeigt. D.h. das neue Cookie scheint nicht akzeptiert zu werden, es wird allerdings auch nicht als nicht akzeptiert angezeigt...
                    Auf der Chrome Entwicklerkonsole konnte ich soeben feststellen, dass das neu zu setzende Cookie lediglich als Session Cookie angezeigt wird, obwohl ich eine Zeit für "expires" angegeben habe in setcookie();. Wie kann das sein?

                  2. Können falsche/fehlende Angaben im Request Header zu diesem Verhalten führen bzw. den Browser dazu bewegen, ein Cookie zu verweigern?
                    Mein Request Header sieht derzeit folgendermaßen aus:

                      
                    	xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
                    	xmlhttp.setRequestHeader("Content-length", params.length);  
                    	xmlhttp.setRequestHeader("Connection", "close");  
                    
                    
                    1. Hi,

                      Können falsche/fehlende Angaben im Request Header zu diesem Verhalten führen bzw. den Browser dazu bewegen, ein Cookie zu verweigern?

                      Nicht, dass ich wüsste.
                      Generell sollen auch bei AJAX-Requests Cookies problemlos übermittelt werden.

                      Mein Request Header sieht derzeit folgendermaßen aus:

                      xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
                      

                      xmlhttp.setRequestHeader("Content-length", params.length);
                      xmlhttp.setRequestHeader("Connection", "close");

                        
                      Ändert sich was am Verhalten, wenn du diese Header teilweise oder komplett auskommentierst?  
                      (Rein auf Senden und Empfangen des Cookies bezogen; dass deine serverseitige Verarbeitung da nicht mehr mitspielen wird, ist abzusehen. Ersetze die also erst mal durch ein ganz simples Testscript, das nur Cookies liest und setzt, und sonst nichts macht.)  
                        
                      MfG ChrisB  
                        
                      
                      -- 
                      RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
                      
                    2. Hallo,

                      Können falsche/fehlende Angaben im Request Header zu diesem Verhalten führen bzw. den Browser dazu bewegen, ein Cookie zu verweigern?

                      nein, aber sie können dazu führen, dass das serverseitige Script nicht das tut, was du erwartest.

                      xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                      xmlhttp.setRequestHeader("Content-length", params.length);

                      Das sieht verdächtig aus. Die length-Eigenschaft eines Javascript-Arrays gibt die Anzahl der Elemente an, der HTTP-Header Content-Length jedoch die Anzahl der Bytes. Oder ist params in diesem Fall der fertig aufbereitete und URL-codierte String?

                      So long,
                       Martin

                      --
                      Die letzten Worte des Neandertalers:
                      Möchte doch zu gern wissen, was in der Höhle ist ...
                      Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
                      1. Hallo,

                        Können falsche/fehlende Angaben im Request Header zu diesem Verhalten führen bzw. den Browser dazu bewegen, ein Cookie zu verweigern?

                        nein, aber sie können dazu führen, dass das serverseitige Script nicht das tut, was du erwartest.

                        xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
                        xmlhttp.setRequestHeader("Content-length", params.length);  
                        

                        Das sieht verdächtig aus. Die length-Eigenschaft eines Javascript-Arrays gibt die Anzahl der Elemente an, der HTTP-Header Content-Length jedoch die Anzahl der Bytes. Oder ist params in diesem Fall der fertig aufbereitete und URL-codierte String?

                        Sehr richtig, params ist der fertige URL-codierte String in Form 'param=value&param2=value2'.

                        Gruß,
                        Jonas

        3. Hallo,

          Der Rückgabewert von setcookie sagt absolut nichts darüber aus, ob der Cookie auch beim Client angekommen ist oder von diesem akzeptiert wurde.

          besser: ... ob der Cookie auch beim Client ankommen wird und/oder von diesem akzeptiert wird.
          Schließlich legt man mit setcookie() ja nur den entsprechenden HTTP-Header bereit, der dann mit der Response an den Client übermittelt wird.

          Ciao,
           Martin

          --
          F: Was ist schlimmer: Alzheimer oder Parkinson?
          A: Parkinson. Lieber mal ein Bier vergessen zu zahlen, als eins verschütten.
          Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
          1. Hi Martin,

            Der Rückgabewert von setcookie sagt absolut nichts darüber aus, ob der Cookie auch beim Client angekommen ist oder von diesem akzeptiert wurde.

            besser: ... ob der Cookie auch beim Client ankommen wird und/oder von diesem akzeptiert wird.

            Von mir aus ... wenn du unbedingt Erbsen spalten, Korinthen zählen oder Haare kacken willst :-)

            Schließlich legt man mit setcookie() ja nur den entsprechenden HTTP-Header bereit, der dann mit der Response an den Client übermittelt wird.

            Du störst dich also an der gewählten Zeitform?
            Nun, zu dem Zeitpunkt, an Jonas die im Script gemachte Kontrollausgabe zu Gesicht bekommt, dürfte über Ankommen und Akzeptieren aber auch schon entschieden sein, in sofern macht das m.E. hier einen eher theoretischen Unterschied.

            MfG ChrisB

            --
            RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
            1. Hallo,

              besser: ... ob der Cookie auch beim Client ankommen wird und/oder von diesem akzeptiert wird.
              Von mir aus ... wenn du unbedingt Erbsen spalten, Korinthen zählen oder Haare kacken willst :-)

              ich bin für Erbsen kacken oder Haare zählen. Korinthen spalten darfst du selber. ;-)

              Du störst dich also an der gewählten Zeitform?

              Ja, weil sie einen falschen Eindruck der Abläufe erwecken könnte.

              Nun, zu dem Zeitpunkt, an Jonas die im Script gemachte Kontrollausgabe zu Gesicht bekommt, dürfte über Ankommen und Akzeptieren aber auch schon entschieden sein, in sofern macht das m.E. hier einen eher theoretischen Unterschied.

              Das stimmt natürlich - ich wollte nur nochmal klarstellen, dass allein durch setcookie() "noch nichts passiert", sondern quasi nur in Auftrag gegeben wird. Erfahrungsgemäß verlieren da viele den Überblick. Fürs Begreifen der Server/Client-Rollenverteilung reicht es oft noch (auch nicht immer), aber wenn zum #C0FFEE dann noch Kekse gereicht werden, ist die Verwirrung perfekt.

              Ciao,
               Martin

              --
              Eifersucht ist so alt wie die Menschheit: Als Adam einmal spät heimkam, zählte Eva sofort seine Rippen.
              Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
      2. refreshContent aktualisiert lediglich die Daten innerhalb des Widget-Objekts, in diesem Beispiel (RSS Widget) wird ein Daten Array aktualisiert. Das funktioniert tadellos. Dazu folgendes Beispiel:

        Sorry, falsches Beispiel, hier das Richtige:

          
        if($widget->refreshContent()) {  
        	if(setcookie(COOKIE_NAME, base64_encode(serialize($page)), getCookieExpDate(), '/')) echo "<h2>SUCCESS</h2>\n";  
        	var_dump($page);  
        	$page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));  
        	echo "<br /><br />\n";  
                var_dump($page);  
        
        
  2. hi,

    Findet ihr ein Problem in diesem Code?

    Nein, aber Dein Konzept würde ich dahingehend ändern, dass im Cookie die Seite klassifiziert wird.

    Ergänzend zu ChrisB: Cookie einfach überschreiben. Löschen kann nur der User/UserAgent.

    Hotti

    1. Hi,

      Nein, aber Dein Konzept würde ich dahingehend ändern, dass im Cookie die Seite klassifiziert wird.

      Wie meinste du das genau? Ich könnte natürlich nur eine Konfiguration der Seite ablegen, welche Widgets auf der Seite enthalten sind usw. Allerdings möchte ich mit der Setzung des Cookies eine Art Chaching erzielen. D.h. ein Widget, bspw. wieder das RSS Objekt soll ältere Einträge enthalten, falls die Ressource nicht verfügbar ist, wenn der User die Seite betritt.

  3. Hi,

    eine generelle Anmerkung dazu noch:

    $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));

    Sowas ist „böse“.

    Du weißt nicht, welche Daten dir ein Client als Cookie schicken wird - die können manipuliert sein, und im worst case kann deren Deserialisierung schädliche Auswirkungen haben.
    unserialize sollte nicht für Daten benutzt werden, die manipuliert werden können.

    Du solltest ein anderes Datenformat nutzen, um die Daten im Cookie zu speichern - bspw. JSON.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    1. Hi,

      Hi,

      eine generelle Anmerkung dazu noch:

      $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));

      Sowas ist „böse“.

      Du weißt nicht, welche Daten dir ein Client als Cookie schicken wird - die können manipuliert sein, und im worst case kann deren Deserialisierung schädliche Auswirkungen haben.
      unserialize sollte nicht für Daten benutzt werden, die manipuliert werden können.

      Du solltest ein anderes Datenformat nutzen, um die Daten im Cookie zu speichern - bspw. JSON.

      Da hast du recht. Allerdings könnten die JSON Daten ebenfalls manipuliert werden und ausführbaren JS-Code enthalten. Bist du sicher, dass sich das Sicherheitsrisiko bei Verwendung von JSON verringert?

      Besten Gruß,
      Jonas

      1. Hi,

        Da hast du recht. Allerdings könnten die JSON Daten ebenfalls manipuliert werden

        Da JSON (u.a.) als Datenübertragungsformat für's Web konzipiert ist, darf davon ausgegangen werden, dass dies bereits entsprechend berücksichtigt worden ist bei der Spezifikation des Parsers.
        Bei unserialize ist das nicht so.

        und ausführbaren JS-Code enthalten.

        Das ist doch egal, so lange du die Daten nicht in einen Kontext bringst, in dem JavaScript überhaupt als solches interpretiert wird.

        MfG ChrisB

        --
        RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    2. Hi!

      eine generelle Anmerkung dazu noch:

      $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));
      Sowas ist „böse“.

      Find ich nicht.

      Du weißt nicht, welche Daten dir ein Client als Cookie schicken wird - die können manipuliert sein, und im worst case kann deren Deserialisierung schädliche Auswirkungen haben.

      Ich wüsste jetzt nicht welche das sein sollen.

      unserialize sollte nicht für Daten benutzt werden, die manipuliert werden können.

      Es liefert einen Variablencontainer zurück, in dem jeder belibige PHP-Variablentyp enthalten sein kann. Der wird in dem Fall $page zugewiesen und fertig ist. Die einzige Code-Ausführung, die mir einfällt, wäre __wakeup() bei Objekten.

      Lo!

      1. Hi,

        $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));
        Sowas ist „böse“.

        Find ich nicht.

        Es gab in der Vergangenheit m.W. einige Bugs in der Implementierung von unserialize.

        http://sharpesecurity.blogspot.com/2010/07/php-unserialize-vulnerability.html berichtet bspw. von einem, der in PHP 5.2 <= 5.2.13 und PHP 5.3 <= 5.3.2 noch vorhanden ist.

        Stefan Esser hat IIRC auch schon davor gewarnt.

        Die einzige Code-Ausführung, die mir einfällt, wäre __wakeup() bei Objekten.

        Die kann ja schon ausreichend sein.
        Dabei muss es sich ja nicht mal um ein Objekt handeln, welches das jeweilige Script aktiv nutzt - wenn die Klassendefinition irgendwo in seinem Umfeld verfügbar ist, bspw. über einen autoload-Mechanismus, reicht das u.U. schon aus, um Schaden anzurichten.
        Stell dir das jetzt noch in Kombination mit irgendeinem Open-Source-System vor, wo ich mir anschauen kann, welche Klassen es gibt und wie die ggf. beim Aufwachen Daten verändern - dann reicht mir u.U. schon ein Plugin, welches nur aus einem Cookie per unserialize den Namen und die E-Mail-Adresse eines Nutzers für eine Kommentarformular-Vorbelegung wieder herstellen will, um damit Unheil anzurichten.

        MfG ChrisB

        --
        RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
      2. Moin!

        $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));
        Sowas ist „böse“.

        Find ich nicht.

        In den Daten von Serialize ist abgelegt, welche Klasse als Objekt genutzt wurde. Manipuliere ich also die in unserialize gefütterten Daten, kann ich beliebig bestimmen, welche Klasse wiederhergestellt und mit den weiteren Eigenschaften gefüllt wird.

        Bei allen anderen Variablentypen hat man es tatsächlich nur mit Daten zu tun - aber genau hier beeinflusst man direkt auch den zu nutzenden Code. Wenn an dieser Stelle keine exakte Validierung auf gültige Angaben greift, halte ich das für eine große Sicherheitslücke, wenngleich deren Ausnutzung ohne Frage eine Herausforderung ist, die ggf. Insiderwissen (sprich: Kenntnis des ausgeführten Codes) erfordert.

        Es liefert einen Variablencontainer zurück, in dem jeder belibige PHP-Variablentyp enthalten sein kann. Der wird in dem Fall $page zugewiesen und fertig ist. Die einzige Code-Ausführung, die mir einfällt, wäre __wakeup() bei Objekten.

        Ich kann halt jede beliebige andere Klasse, die dem Skript an dieser Stelle zur Verfügung steht, instanziieren lassen. Wenn Autoloading im Spiel ist, wird das richtig spannend.

        - Sven Rautenberg

        1. Hi!

          $page = unserialize(base64_decode($_COOKIE[COOKIE_NAME]));
          Sowas ist „böse“.
          Find ich nicht.
          In den Daten von Serialize ist abgelegt, welche Klasse als Objekt genutzt wurde. Manipuliere ich also die in unserialize gefütterten Daten, kann ich beliebig bestimmen, welche Klasse wiederhergestellt und mit den weiteren Eigenschaften gefüllt wird.
          Bei allen anderen Variablentypen hat man es tatsächlich nur mit Daten zu tun - aber genau hier beeinflusst man direkt auch den zu nutzenden Code.

          Das ist genauso pauschal gesagt wie das obige "böse". Code wird nicht serialisiert und es gibt keinen Weg, dass beim unserialize() neuer Code entsteht. Es werden lediglich Objekte von vorhandenen Klassen mit den serialisierten Eigenschaften wiederhergestellt. Sollte sich der auszunutzende Code nicht bereits im Script befinden, kommt nichts hinzu. Das Problem ist lediglich, wenn im Script $page ein Objekt darstellt (ist hier der Fall), dann kann mit obigem Code $page ein Objekt einer anderen Klasse als vorgesehen werden. Und nur wenn diese andere Klasse gleichnamige Methoden hat, die eigentlich von der geplanten Klasse von $page hätten aufgerufen werden sollen, dann wird der Code der anderen Klasse aufgerufen. Würde $page gar nicht als Objekt behandelt, kommen lediglich die magischen Methoden wie __toString() in Frage.

          Wenn an dieser Stelle keine exakte Validierung auf gültige Angaben greift, halte ich das für eine große Sicherheitslücke, wenngleich deren Ausnutzung ohne Frage eine Herausforderung ist, die ggf. Insiderwissen (sprich: Kenntnis des ausgeführten Codes) erfordert.

          So pauschal würde ich mich hüten, die Größe der Sicherheitslücke bestimmen zu wollen. Da muss schon einiges zusammenkommen, um ausgenutzt zu werden. Aber ja, Sicherheitslücke bleibt Sicherheitslücke, egal ob sie "groß" oder "klein" ist.

          Es liefert einen Variablencontainer zurück, in dem jeder belibige PHP-Variablentyp enthalten sein kann. Der wird in dem Fall $page zugewiesen und fertig ist. Die einzige Code-Ausführung, die mir einfällt, wäre __wakeup() bei Objekten.
          Ich kann halt jede beliebige andere Klasse, die dem Skript an dieser Stelle zur Verfügung steht, instanziieren lassen. Wenn Autoloading im Spiel ist, wird das richtig spannend.

          Das ist eine der Einschränkungen, es muss sich um bereits vorhandene Klassen handeln. Und das Autoloading muss man auch erst einmal beeinflussen können. Wenn ich das kann, brauch ich keine Kekse, um ungewollten Code ausführen zu lassen, denn dann kann ich gleich den gewüschten Code unterschieben. Aber du zieltest auf __autoload() ab und nicht auf auto_prepend_file. Dann gelten wieder obige Einschränkungen, dass gleichnamige Methoden vorhanden sein müssen.

          Eine einfache Lösung gegen das Instantiieren ungewollter Klassen mit dieser Unserialize-Lösung wäre, anschließend die Klasse zu prüfen. Dass alle Eigenschaften wie Benutzereingaben zu behandeln sind, unterscheidet sich nicht vom Deserialisieren anderer Typen.

          Lo!

          1. Hi,

            Das ist genauso pauschal gesagt wie das obige "böse". Code wird nicht serialisiert und es gibt keinen Weg, dass beim unserialize() neuer Code entsteht.

            Es kann aber bestehender Code zur Ausführung gebracht werden - und das ohne direkten Zugriff auf diesen Code.

            Es werden lediglich Objekte von vorhandenen Klassen mit den serialisierten Eigenschaften wiederhergestellt. Sollte sich der auszunutzende Code nicht bereits im Script befinden, kommt nichts hinzu.

            Richtig.
            Aber das „jeder“ von außerhalb von einer beliebigen Klasse in meinem System eine Instanz erstellen kann, muss ich nicht unbedingt wollen.

            Würde $page gar nicht als Objekt behandelt, kommen lediglich die magischen Methoden wie __toString() in Frage.

            Die reichen doch unter entsprechend günstigen Umständen aber schon.
            $page braucht es dazu gar nicht - es reicht, wenn du im Script etwas „unverdächtig“ aussehendes wie
            echo unserialize($_COOKIE['foo']);
            stehen hast.

            Bei entsprechendem Cookie-Inhalt erzeugt unserialize ein Objekt, und dessen __toString-Methode wird aufgerufen, sofern vorhanden - und das würde Ausführung von Code bedeuten, die in obiger unschuldiger Script-Zeile sicher nicht beabsichtigt war.

            So pauschal würde ich mich hüten, die Größe der Sicherheitslücke bestimmen zu wollen. Da muss schon einiges zusammenkommen, um ausgenutzt zu werden. Aber ja, Sicherheitslücke bleibt Sicherheitslücke, egal ob sie "groß" oder "klein" ist.

            Mehrere kleine Lücken, geschickt vom Angreifer kombiniert, ergeben eine große ...

            Ich kann halt jede beliebige andere Klasse, die dem Skript an dieser Stelle zur Verfügung steht, instanziieren lassen. Wenn Autoloading im Spiel ist, wird das richtig spannend.

            Das ist eine der Einschränkungen, es muss sich um bereits vorhandene Klassen handeln. Und das Autoloading muss man auch erst einmal beeinflussen können.

            Wenn es pauschal genug umgesetzt ist, so wie es bspw. das Primitivbeispiel im Manual „vormacht“,

            function __autoload($class_name) {  
                include $class_name . '.php';  
            }
            

            lacht sich der Angreifer doch schon ins Fäustchen.

            Ja, ich weiß, würdest *du* nicht so umsetzen - machen andere Leute aber vielleicht, und denken sich nicht viel dabei. Und mit dieser „klitzekleinen-was-soll-denn-schon-passieren“-Schwachstelle, und der ebenso unscheinbaren, die das Unserialisieren eines Cookie-Wertes darstellt - haben wir jetzt zwei, die in *Kombination* schon sehr viel mächtiger sein können, als sie einzeln den Anschein haben mögen.

            Wenn ich das kann, brauch ich keine Kekse, um ungewollten Code ausführen zu lassen, denn dann kann ich gleich den gewüschten Code unterschieben.

            Nein - Zugriff auf dein System o.ä. brauche ich im geschilderten Szenario in keinster Weise.

            Aber du zieltest auf __autoload() ab und nicht auf auto_prepend_file. Dann gelten wieder obige Einschränkungen, dass gleichnamige Methoden vorhanden sein müssen.

            Nur dann, wenn das durch Unserialize erstellte Objekt „weiterverarbeitet“ werden soll. Eine __wakeup- oder __toString-Methode kann allein durch die Erzeugung bzw. durch Ausgabe in einem String-Kontext aufgerufen werden.

            Ein weiteres „ich weiß“ - diese Methoden würden bei *dir* keinerlei Schaden anrichten können, OK.
            Aber willst du für alles, was im Umfeld eines Open-Source-Systems wie bspw. Wordpress an Plugins und sonstigem durch die Gegend schwirrt, deine Hand ins Feuer legen ...?

            Ein Hobby-Scripter, der in seinem hübschen kleinen Kommentar-Plugin die unserialize-Lücke einbaut, um wie schon in meinem anderen Beitrag als Beispiel erwähnt die Vorbelegung des Namens-Feldes in einem Formular zu realisieren,
            plus ein weiterer, der vollkommen unabhängig vom ersten es für eine gute Idee hält, seine Klasse XY mit einer __wakeup-Methode zu versehen, die beim Erwachen des Objektes irgendwelche Daten „aufräumt“ (Dateien/Datensätze löscht)

            • macht in der Summe bei einem Open-Source-System, wo ich mit den kompletten Quellcode anschauen und in Ruhe nach solchen „kleinen“ Lücken durchsuchen kann, eine Lücke, wo ich durch einen simplen HTTP-Request von außen Daten im System zerstören kann, ohne dass das jemals von irgend jemandem so beabsichtigt war.

            Eine einfache Lösung gegen das Instantiieren ungewollter Klassen mit dieser Unserialize-Lösung wäre, anschließend die Klasse zu prüfen.

            Im Falle einer bei der Re-Instantiierung aufgerufenen __wakeup-Methode - too late.

            MfG ChrisB

            --
            RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
  4.   setCookie(COOKIE\_NAME, '', time()-42000, '/');  
    

    Versuche mal das Cookie weniger Tief in die Vergangenheit zu schicken.
    Ich habe dunkel in Erinnerung, dass der Firefox das nicht mag.

    Teste doch bitte mal:
    setCookie(COOKIE_NAME, '', time()-10, '/');

    Hierbei kann ich mich aber durchaus irren.

    Gruß,
    jumini

    1. Hi,

      Teste doch bitte mal:
      setCookie(COOKIE_NAME, '', time()-10, '/');

      Löst das Problem leider nicht. Das Cookie wird ja auch gelöscht, wenn ich setcookie mit expires=vergangenheit setze, nur das neue wird nicht gesetzt.

      Gruß,
      Jonas

  5. Hallo Leute,

    Ich habe, wie so häufig bei der Diagnose, viel zu weit unten nach dem Fehler gesucht, bis ich mich an eine weit zurückliegende Vorlesung Kommunikations- und Netzwerktechnik erinnert habe. Das Problem könnte bei der maximalen Größe des HTTP-Headers liegen. Die meisten Apache-Server setzen das Limit bei 8KB, mein Cookie alleine hat häufig um die 8KB.
    Was meint ihr?

    Viele Grüße,
    Jonas

    1. Hi,

      Das Problem könnte bei der maximalen Größe des HTTP-Headers liegen. Die meisten Apache-Server setzen das Limit bei 8KB, mein Cookie alleine hat häufig um die 8KB.

      Na das hättest du mal gleich sagen können :-)

      Die ursprüngliche „Spezifikation“ für Cookies von Netscape hat schon festgelegt,

      “There are limitations on the number of cookies that a client can store at any one time. This is a specification of the minimum number of cookies that a client should be prepared to receive and store.

      • 300 total cookies
      • 4 kilobytes per cookie, where the name and the OPAQUE_STRING combine to form the 4 kilobyte limit.”

      Und auch RFC 2965 - HTTP State Management Mechanism sieht das noch immer ähnlich,
      “user agents SHOULD provide each of the following minimum capabilities individually, although not necessarily simultaneously:
      *  at least 300 cookies
      *  at least 4096 bytes per cookie”

      Was meint ihr?

      So viel Daten sendet man generell nicht per Cookie hin und her, das verzögert den kompletten Netzwerkverkehr und wirkt sich damit schlecht auf die Gesamtperformance der Webanwendung aus.

      Nutze Sessions. Punkt.

      MfG ChrisB

      --
      RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
      1. Nutze Sessions. Punkt.

        Wäre auch mein nächster Gedanke gewesen. Der Cache wird sich dann wohl auf die aktive Session beschränken, was ja auch reichen sollte.

        Wie gesagt, vielen Dank für die Mühe und das nächste Mal werde ich nicht direkt so tief einsteigen :)

        Besten Gruß,
        Jonas

    2. Hi,

      mein Cookie alleine hat häufig um die 8KB.

      Die Dinger heißen Cookie, nicht Sheet Cake!

      cu,
      Andreas

      --
      Warum nennt sich Andreas hier MudGuard?
      O o ostern ...
      Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
      1. Hallo Andreas,

        mein Cookie alleine hat häufig um die 8KB.
        Die Dinger heißen Cookie, nicht Sheet Cake!

        YMMD!  :-)

        Ciao,
         Martin

        --
        Auch mit eckigen Radios kann man Rundfunk hören.
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(