31

An external company has done some penetration tests on the ASP.NET MVC 5 application i'm working on.

An issue that they raised is described below

A cookie linked with session Management is called AspNet.ApplicationCookie. When entered manually,the application authenticates the user. Even though the user logs out from the Application,the cookie is still valid. This means,the old session cookie can be used for a valid authentication within unlimited timeframe. In the moment the old value is inserted, the application accepts it and replaces it with a newly generated cookie. Therefore, if the attacker gains access to one of the existing cookies, the valid session will be created,with the same access as in the past.

We're using ASP.NEt Identity 2.2

Here's our logout action on the account controller

 [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff()
    {
        AuthenticationManager.SignOut();
        return RedirectToAction("Login", "Account");
    }

in startup.auth.cs

 app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            ExpireTimeSpan = TimeSpan.FromHours(24.0),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator
             .OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                 validateInterval: TimeSpan.FromMinutes(1.0),
                 regenerateIdentityCallback: (manager, user) =>
                     user.GenerateUserIdentityAsync(manager),
                 getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))

            }
        });

I would have thought that the framework would have taken care of invalidating an old session cookie but browsing through the Owin.Security source code it appears not.

How do i invalidate the session cookie on logout?

edit on Jamie Dunstan's Advice i've added AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); but then has made no difference. I can still still log out of the application, clone a previously authenticated request in Fiddler, and have it accepted by the application.

Edit : My updated Logoff method

 [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> LogOff()
    {
        var user = await UserManager.FindByNameAsync(User.Identity.Name);

        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
        await UserManager.UpdateSecurityStampAsync(user.Id);

        return RedirectToAction("Login", "Account");
    }
Community
  • 1
  • 1
MrBliz
  • 5,830
  • 15
  • 57
  • 81
  • 1
    Have you tried replacing `AuthenticationManager.Signout();` with `AuthenticationManager.Signout(DefaultAuthenticationTypes.ApplicationCookie);`? The parameterless signout appears to be a bit inconsistent. – Jamie Dunstan Dec 01 '15 at 14:49
  • Cheers. Done, but i can still log out of the application, and then clone a previously authenticated request in fiddler and have it accepted – MrBliz Dec 01 '15 at 15:19
  • I've also removed the Expiretimespan from startup.Auth. No difference. – MrBliz Dec 01 '15 at 15:20
  • 1
    The best idea I can come up with is to manually call `UserManager.UpdateSecurityStampAsync(userId);` after calling `SignOut`. Can you try this and see if it works? – Jamie Dunstan Dec 01 '15 at 16:36
  • 1
    That did not work. Still able to use a previously authenticated request via fiddler after the user has logged out. – MrBliz Dec 01 '15 at 17:15

3 Answers3

10

Make sure you use AuthenticationManager.Signout(DefaultAuthenticationTypes.ApplicationCookie); as correctly suggested by Jamie.

Being able to login with the same cookie again is by design. Identity does not create internal sessions to track all logged-in users and if OWIN gets cookie that hits all the boxes (i.e. copies from the previous session), it'll let you login.

If you still can login after the security stamp is updated, most likely OWIN can't get a hold of ApplicationUserManager. Make sure you have this line just above the app.UseCookieAuthentication

app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

Or if you are using DI take ApplicationUserManager from DI:

app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());

Also reduce the validateInterval: TimeSpan.FromMinutes(30) to lower value - I usually settle for couple minutes. This is how often Identity compares values in auth-cookie to the values in the database. And when the comparison is done, Identity regenerates the cookie to update timestamps.

trailmax
  • 34,305
  • 22
  • 140
  • 234
  • 1
    And here is another question with almost the same answer: http://stackoverflow.com/a/34016721/809357 – trailmax Dec 01 '15 at 18:30
  • Thank you, i can confirm all the above is in place. I've changed the validateInterval to validateInterval: TimeSpan.FromMinutes(1.0). I can make an authernticated request, log out, wait for 5 minutes and clone that request and it still be authenticated. Are you saying that should happen by design? – MrBliz Dec 02 '15 at 09:47
  • @MrBliz correct - by design. Unless you change the Security Stamp old cookie will authenticate you. Cookie stores username, userId, security stamp, some timestamps and bits of other information (here is a bit more info: http://tech.trailmax.info/2014/08/aspnet-identity-cookie-format/). And `SignOut()` methods merely kills the cookie. But if the same cookie is replayed again, it will accept it. Same way you sign-out from one browser, but the other one is still authenticated. So if you need to kill all sessions, you need to update security stamp at the same time you sign-out. – trailmax Dec 02 '15 at 10:14
  • 1
    Thank you very much. Glad i tweeted you :) – MrBliz Dec 02 '15 at 13:08
  • @MrBliz Bear in mind you can hit this issue: http://stackoverflow.com/a/33670509/809357 - it looks like a bug. – trailmax Dec 02 '15 at 16:48
  • @trailmax - just stumbled across this answer as I have exactly the same issue as the OP. This answer has been very helpful in understanding that this is expected behavior, so thank you! However, I'm not totally clear on the conclusion and as to whether the OP's solution ever worked. "Unless you change the Security Stamp old cookie will authenticate you" - does this mean that _by_ changing the Security Stamp we can put in place a workaround? I.e. will calling `UserManager.UpdateSecurityStampAsync` on sign-out along with a short `validateInterval` work? Logically, I think it should. – Phil Bellamy Mar 14 '19 at 13:47
  • 1
    @PhilBellamy your thinking is correct. Shorten `validateInterval` and update security stamp. – trailmax Mar 14 '19 at 14:22
0

Trailmax's answer is spot on, I thought I would add that if someone is trying to do this while also using ASP.NET Boilerplate, the following is what I used to make this work:

app.CreatePerOwinContext(() => IocManager.Instance.Resolve<UserManager>());

I originally had:

app.CreatePerOwinContext(() => IocManager.Instance.ResolveAsDisposable<UserManager>());

and is wasn't working.

0

you were on the right way. Indeed the easiest way is to update user SecurityStamp but normally executing it doesn't lead to success because actualy credentials aren't changed and it remains the same in db. Solution, try this:

private string NewSecurityStamp()
        {
            return Guid.NewGuid().ToString();
        }

private async Task RegenerateSecurityStamp(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
            if (user != null)
            {
                user.SecurityStamp = NewSecurityStamp();
                await _userStore.UpdateAsync(user);
            }
    }

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> LogOff()
    {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
        await RegenerateSecurityStamp(User.Identity.GetUserId());
        return RedirectToAction("Login", "Account");
    }
Vlad Hrona
  • 387
  • 3
  • 7