Vertauschte Sessions (OutputCache und Cookieless Sessions)

Ich wurde vor kurzem mit einem Problem konfrontiert, bei dem ich meinem Kunden natürlich weiterhelfen wollte. Bei einem Web-Projekt wurden sporadisch Sessions “vertauscht” - so sah ein Benutzer A plötzlich die Userdaten eines Benutzers B. Das kam eher selten vor, aber jeder einzelne Fall ist natürlich unschön.

Meine erste Vermutung war, dass das Session-Objekt (oder eine der darin enthaltenen Variablen) evtl. in statischen Variablen gelandet ist. Das soll wohl öfter mal vorkommen und sieht z.B. so aus.

1
2
3
4
5
6
7
8
9
private static Session _sess;
public static void DoSomething() {
if (_sess == null) {
_sess = HttpContext.Current.Session;
}
if (_sess != null) {
// ...
}
}

Zugegeben, sieht recht harmlos aus - ist aber folgenschwer. Ich hab den Entwickler der Webseite kontaktiert und ihn gefragt, ob so etwas ähnliches evtl. enthalten ist. Der machte sich auch direkt auf die Suche nach ähnlichen Konstrukten, blieb aber erfolglos. Danach bekam ich direkten Zugriff auf den Quellcode.

Ich hab den Code natürlich nochmal durchforstet (und die Fremd-Assemblies analysiert) - aber auch ich blieb ohne Erfolg bei meiner Suche (war ja auch überheblich zu denken, dass die anderen nicht richtig suchen würden - ich schäm mich *g*). Die Cache-Einstellungen schienen in Ordnung. Ich hab daraufhin mehrere Tests mit PhantomJS erstellt und durchgehend geprüft, ob die Session irgendwann verloren geht oder die Daten andere werden - aber auch hier ohne jeden Lichtblick. So ging das jetzt mehrere Tage.

Heute hatte ich was ganz anderes vor. Dabei musste ich Cookies in meinem Browser deaktiviert und kam eher zufällig auf die Seite - da hab ich es dann auch endlich mal gesehen: Userdaten eines anderen Benutzers. Und nun war auch endlich klar, wie es zum “vertauschen” der Sessions kam - nämlich gar nicht ;) Wir haben vielmehr - in einem Teilbereich - alle das Gleiche gesehen.

Die ASP.NET - Seite verwendet Cookies im AutoDetect-Modus (<session cookieless="AutoDetect" />). Es hätte aber auch UseUrl sein können - damit wäre es ebenso falsch gelaufen (allerdings wahrscheinlich leichter entdeckt).

Zusätzlich war bei einem ASP.NET Control (ein Footer) angegeben, dass die Ausgabe gecached werden kann. Das ist mir bei der vorherigen Durchforstung zwar aufgefallen, aber nicht wirklich negativ. Die Angabe sah so aus

1
<%@ OutputCache Duration="6000" VaryByParam="none" %>

Also auch hier an sich nichts schlimmes. Die HTML-Elemente waren allesamt Links zum Impressum und ähnlichen Seiten. Damit die Cookieless-Session beim Aufruf des Impressums nicht verloren geht, müssen natürlich auch diese Links mit Response.ApplyAppPathModifier() angepasst werden. Argh. Selbstverständlich wird die SessionID hier mitgecached - nur gesehen hab ich es nicht.

Der erste User nach den 6000 Sekunden gibt vor, wie der Footer aussieht.
Sind bei dem User Session-Cookies im Browser erlaubt, klappt es überall (die Cookieless-User verlieren Ihre Session, aber das ist weitaus weniger schlimm als fremde Userdaten zu sehen).
Ist der Browser des “führenden” Users dagegen so eingestellt, dass Session-Cookies abgelehnt werden, bekommen alle dessen Session angezeigt…

Nun kann man entweder den OutputCache ganz abstellen, oder aber die Angabe etwas erweitern zu

1
<%@ OutputCache Duration="6000" VaryByParam="none" VaryByCustom="session" %>

Hierfür muss aber in der global.asax noch die Funktion GetVaryByCustomString überschrieben werden, in etwa so:

1
2
3
4
5
6
7
8
public override string GetVaryByCustomString(HttpContext context, string custom) {
if (custom == "session") {
if (context.Session != null) {
return context.Session.SessionID;
}
}
return base.GetVaryByCustomString(context, custom);
}

So funktionieren auch OutputCache und Cookieless Sessions problemlos zusammen… Und beim nächsten Mal seh ich das auch gleich ;)