Laden...

Cookie überschreiben in ASP.NET Core

Erstellt von Cord Worthmann vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.769 Views
C
Cord Worthmann Themenstarter:in
1.215 Beiträge seit 2004
vor 4 Jahren
Cookie überschreiben in ASP.NET Core

Hallo Foristi,

gehe ich Recht in der Annahme, dass man bestehende Cookies in ASP.NET Core nicht mit einem neuen Wert überschreiben kann, oder habe ich da was übersehen?

In meiner Anwendung können Nutzer dauerhaft angemeldet bleiben. Dazu verwende ich eine Kombination aus AuthToken per Cookie und UserAgent.

Nun möchte ich die Sicherheit noch weiter verbessern, indem jedes Mal, wenn ein Nutzer auf diese Weise identifiziert wurde, der AuthToken-Wert in der DB und im Cookie auf einen neuen geändert wird.

Nur habe ich leider keine Methode gefunden, wie ich den Wert eines bestehenden Cookies ändern könnte. Selbst Folgendes hat zu keinem Ergebnis geführt:

Response.Cookies.Delete(".AuthToken");
Response.Append(".AuthToken", "New Value", options);

Beim nächsten Seitenaufruf steht immer noch der alte Wert im Cookie, während sich der in der DB natürlich geändert hat, wodurch die automatische Authentifizierung nun fehlschlägt, und der User sich händisch neu einloggen muss.

Meine Frage also noch mal, kann man Werte bestehnder Cookies überschreiben, oder kann ich mein Sicherheitskonzept so nicht umsetzen?

Gruß, Cord

16.807 Beiträge seit 2008
vor 4 Jahren

Das hat mit ASP.NET nichts zutun sondern ist Teil des HTTP Standards.

Wenn ein Cookie hinzugefügt wird, überschreibt der Browser ein Cookie, sofern er existiert.
Alternativ wird er einfach gesetzt. Auch ein Delete() macht nichts anderes als ein leeren Cookie mit abgelaufener Expire-Time zu setzen, um so den vorhandenen Cookie beim Client zu überschreiben.
Durch die abgelaufene Zeit verwirft der Browser dann automatisch den Cookie.

Daher ist ein "erneutes Hinzufügen" einfach auch ein Überschreiben.

Nun möchte ich die Sicherheit noch weiter verbessern, indem jedes Mal, wenn ein Nutzer auf diese Weise identifiziert wurde, der AuthToken-Wert in der DB und im Cookie auf einen neuen geändert wird.

Das hat mit sehr viel zutun, aber nichts mit "höherer Sicherheit". So funktionieren Cookies einfach nicht, so funktioniert HTTP nicht.

Nur weil Du einen Cookie setzt wird ein vorhandener Cookie nicht ungültig.
Willst Du eine Session validieren, dann bleibt Dir nichts anderes übrig als sie auf Server-Seite zu validieren.

Dazu schreibt man i.d.R. zB eine Guid in eine Session Tabelle in die Datenbank.
Bei jedem Request wird dann die Id aus dem Cookie gelesen und mit dem Datenbankeintrag verglichen.
Ist der Eintrag gültig, dann geht die Auth Pipeline durch. Ist kein Eintrag vorhanden, dann musst Du die Auth Pipe unterbrechen.

Insgesamt sieht das aber nach sehr viel gebastel aus. Alle Authentifizierungskriterien liegen beim Client. Das ist nicht sicher.
Wieso nutzt Du nicht das Auth-Framework von ASP.NET Core? Das hat den Fokus auf Security-by-Design und kümmert sich selbst um das Cookie Handling.
Die User Agent Sache, die aus Sicherheitsaspekten kein Mehrwert bietet, kannst Du weglassen und es besser ordentlich machen.

Edit, hier dein Snippet dazu


            // Cookie
            services.ConfigureApplicationCookie(options =>
            {
                // ... settings..
                options.Events = new UserLoginCookieValidationHandler();
            });


    public class UserLoginCookieValidationHandler : CookieAuthenticationEvents
    {
        public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
        {
            ClaimsPrincipal userPrincipal = context.Principal;

            var authorizationProvider = context.HttpContext.RequestServices.GetRequiredService<IUserAuthorizer>();

            if (!authorizationProvider.TryGetUserSessionInfo(userPrincipal, out int userId, out Guid sessionId))
            {
                // session format seems to be invalid
                context.RejectPrincipal();
            }
            else
            {
                IEventDispatcher eventDispatcher = context.HttpContext.RequestServices.GetRequiredService<IEventDispatcher>();
                bool valid = await eventDispatcher.Get(new GetUserSessionStatusQuery(userId, sessionId));
                if (!valid)
                {
                    // session expired or was killed
                    context.RejectPrincipal();
                }
            }
        }
    }

PS: das ist auch der Ort, an dem man die Aktivität einer Session tracken kann, um so die Laufzeit der Session zu erhöhen, sofern diese aktiv verwendet wird.

C
Cord Worthmann Themenstarter:in
1.215 Beiträge seit 2004
vor 4 Jahren

Nein, es geht nicht um Sessions, das erledigt hier ebenfalls NET Core. Es geht darum, länger abwesende User automatisch zu identifizieren - und das erfolgt per Cookie und UserAgent. Die Nutzer müssen auch die Möglichkeit haben, mit mehreren Geräten gleichzeitig angemeldet zu sein. Es geht also um kein Session-Cookie.

Der Gedanke war nun, dass der Nutzer, der sich einmal angemeldet hat und permanenten Login mit diesem Browser wählt, einmal ein Identifier-Cookie erhält und einmal ein AuthToken-Cookie. Beide Werte werden zusammen mit dem UserAgent in der DB gespeichert.

Kommt der Nutzer nun nach längerer Zeit auf die Seite, wird er anhand von Cookies u. UserAgent identifiziert, wenn er auf diesem Gerät/Browser schon mal angemeldet war. Und dann sollten als weiterer Schutz gegen Cookie-Diebstahl der AuthToken-Wert im Cookie und in der DB geändert werden.

Aber da es mir nicht gelingt (weil es wohl nicht geht), den AuthToken-Wert im bestehenden Cookie zu ändern oder in einem Rutsch das Cookie zu löschen und neu zu schreiben, ohne die Seite zwischendurch zurückzuschicken, ist diese Idee wohl hinfällig. Zumindest ist bis jetzt jeder Versuch fehlgeschlagen, das Cookie zu überschreiben. Auch nach einem Redirect hat es immer noch den alten Wert.

Ob man dieses Szenario mit Net-Core-Boardmitteln auch so umsetzen kann, weiß ich nicht. Zumal dabei ja höchstwahrscheinlich auch eine eigene Nutzer-Tabelle angelegt würde.

16.807 Beiträge seit 2008
vor 4 Jahren

Die Nutzer müssen auch die Möglichkeit haben, mit mehreren Geräten gleichzeitig angemeldet zu sein. Es geht also um kein Session-Cookie.

Ein User kann mehrere Login Sessions besitzen - völlig problemlos.
Deckt auch das Cookie Auth-System von ASP.NET Core ab.

Kommt der Nutzer nun nach längerer Zeit auf die Seite, wird er anhand von Cookies u. UserAgent identifiziert, wenn er auf diesem Gerät/Browser schon mal angemeldet war.

Und dann sollten als weiterer Schutz gegen Cookie-Diebstahl der AuthToken-Wert im Cookie und in der DB geändert werden.

Nein.
Der User Agent bietet kein Mehrwert. Alle Auth-Kriertien sind beim Client.
Das ist nicht sicher; bietet keinen Mehrwert. Im Prinzip ist das eine Lücke.

Im Endeffekt führt Dein Konzept dazu, dass wenn ein User sein Browser aktualisiert (zB Chrome im Hintergrund), dann ist er nicht mehr authentifziert, weil sich ja der User Agent aufgrund der Version ändert.
Das kann ja kein gewollter Effekt sein.

Aber da es mir nicht gelingt (weil es wohl nicht geht), den AuthToken-Wert im bestehenden Cookie zu ändern oder in einem Rutsch das Cookie zu löschen und neu zu schreiben,

Mach es über die Validierung in der Auth Pipeline.
Das ist der einzige sichere Weg und Teil des Konzepts.

Ob man dieses Szenario mit Net-Core-Boardmitteln auch so umsetzen kann, weiß ich nicht. Zumal dabei ja höchstwahrscheinlich auch eine eigene Nutzer-Tabelle angelegt würde.

Ja kann man und wo die User gespeichert werden ist sowohl bei der ASP.NET Core Cookie Middleware wie auch bei ASP.NET Core Identity völlig unabhängig.

Edit: wenn ich Dein "AuthToken" richtig verstehe, dann bist Du gar nicht so weit weg von dem Snippet oben mit der SessionId; nennst es halt anders (ein Auth Token ist eigentlich was anderes als eine eindeutige Id im Cookie).
Wenn Du jetzt noch die Boardmittel verwendest, die Dir - außer die Datenbank-Sache - alles schenken, dann biste völlig im ASP.NET Konzept.

C
Cord Worthmann Themenstarter:in
1.215 Beiträge seit 2004
vor 4 Jahren

Dann muss ich mir das mal näher anschauen. Danke soweit.

16.807 Beiträge seit 2008
vor 4 Jahren

Allgemein dazu:
Cookie Validierungen sind im HTTP Konzept immer Client-Seitig - immer.
Das heisst: hast Du ein mal einen validen Cookie für die Authentifizierung ausgestellt, dann kann das auf Server Seite nicht mehr gelöscht werden ("single source of identity").

Jeder, der den Cookie in die Hand bekommt, kann sich damit authentifizieren.

Die einzige Möglichkeit in dieser Pipeline etwas zu kontrollieren, sind eben die Cookie Events.
Dort kannst Du auf der Serverseite einen zweiten Faktor (eben zB eine User Login Session Id) haben, den der Client nicht kontrollieren kann, und die Validierung ablehnen.

Das ist das, was das Snippet oben zeigt.

Siehe dazu auch
Use cookie authentication without ASP.NET Core Identity - React to back-end changes

C
Cord Worthmann Themenstarter:in
1.215 Beiträge seit 2004
vor 4 Jahren

Ein paar Fragen hätte ich noch zu diesem Verfahren.

Gehe ich recht in der Annahme, dass ein so authentifizierter Nutzer dann über die User-Eigenschaft des HttpContexts ansprechbar ist? Und sind somit auch die zuvor festgelegten Claims wieder abrufbar? Und schließlich, wenn ja, wo speichert ASP.NET diese Informationen überhaupt? Im Cookie-Wert?

16.807 Beiträge seit 2008
vor 4 Jahren

Gehe ich recht in der Annahme, dass ein so authentifizierter Nutzer dann über die User-Eigenschaft des HttpContexts ansprechbar ist?

Ja, wobei es sich lohnt das zu abstrahieren, da ansonsten entsprechend die Unit Tests für Deine Controller aufblähen, weil Du überall den Context injizieren musst.

Alternativ eben entsprechende Extensions bauen, damit Tests schlank bleiben


        public static T WithTestContext<T>(this T controller, ClaimsPrincipal user) where T : Controller
        {
            controller.ControllerContext = new ControllerContext
            {
                HttpContext = new DefaultHttpContext
                {
                    User = user
                }
            };
            return controller;
        }
            SettingsController ctrl = new SettingsController(edMock.Object)
                .WithTestContext(WebUserTestData.GetAnonymousUser());

Abstraktionen können zB eigene Parametertypen in Actions sein, die via Model Binder gefüllt werden.
Ich persönlich empfehl diese Art und Weise, weil es wiederverwendbar, übersichtlich, schlank und einfach zu testen ist.

Und sind somit auch die zuvor festgelegten Claims wieder abrufbar?
Und schließlich, wenn ja, wo speichert ASP.NET diese Informationen überhaupt? Im Cookie-Wert?

Ja, alle Werte werden verschlüsselt im Cookie abgelegt. Per default wird dafür das Data Protection System verwendet.
Wird die Applikation via Load Balacing verteilt, dann musst Du entsprechend einen Key hinterlegen, der dann in allen Instanzen verwendet wird.