15

As a result of a security audit, we must prevent an attacker from being able to do a cookie replay attack. Apparently this weakness has been around in the .NET framework for ages. There is also an old Microsoft Knowledge Base article on that subject.

In contrast to the workarounds mentioned in the MS KB, the blog post recommends to mitigate this weakness by implementing the following:

  • When signing in, set a boolean session variable to true.
  • When signing out, set the session variable to false.
  • Check the session variable on each request. If the value is false (or non-existing), return not authenticated.

Is this method sufficient to prevent a cookie replay attack? If it is, we would like to implement this in Sitecore in a way that developers don't have to think about it. Our idea is to override Sitecore's FormsAuthenticationProvider:

  • Override all three Login methods and initialize the boolean session variable if login succeeded.
  • Override the Logout method to set the session variable to false.
  • Override the GetActiveUser method and take action if the conditions are not met.

But how to override GetActiveUser exactly? Do we return Context.Domain.GetAnonymousUser() ?? Domain.GetDefaultAnonymousUser(), like the AuthenticationHelper class does when there is no valid user?

Or is there a better way to transparently implement this in Sitecore?

Relevant information:

  • Website is only accessible with https
  • Cookies are already flagged as httponly and secure
  • On log out, we call AuthenticationManager.Logout(); and Session.Clear();
  • Sitecore v7.2 rev 140314
  • .NET 4.5
  • I would find a way to bind a session with a specific IP, upon login, and when checking for the authenticated user, make sure the IP of the request matches the IP bound to the session. – adragomanov Oct 19 '16 at 9:44
  • 1
    @adragomanov Binding it to a IP address is not safe in my opinion. User can be on same network, can spoof his IP, etc. – Thomas D Oct 19 '16 at 11:16
  • Sure, but I wouldn't worry about that. Spoofing the IP could only allow you send information to the server, but not receive the response back. With that said, using Anti-Forgery Tokens to validate incoming requests won't allow the attacker to do any actions on behalf of the user, since they won't be able to get a valid token, unless ofc, they're able to sniff one from a legit user request. Which brings us to your other concern. If the attacker is on the same network, then stack could easily do a MitM attack and then there's little to nothing you can do to protect that user inho. – adragomanov Oct 19 '16 at 11:58
  • For checking authenticated user, you could also do this: 1) On successful login, set the user info in session object. This could be just the username or a custom object depending on your requirements. Eg: HttpContext.Current.Session["LoggedInUser"] = "abc" 2) Then, in your application where you require to check if the user is logged in, check that session variable. I would also recommend checking for null as per defensive programming principles. 3) On logout, make that session variable null. Something like this: HttpContext.Current.Session["LoggedInUser"] = null Hope this helps – Akshay Mahajan Oct 19 '16 at 13:19
  • The benefit of using session would be no dependency on cookie for authentication as cookies are very vulnerable to various attacks. Also, it is generally a good practice to null out the session variables on logout instead of making them false. – Akshay Mahajan Oct 19 '16 at 13:20
10

It seems I was very close with overriding Sitecore's FormsAuthenticationProvider. With our first attempt we could successfully prevent cookie replay attacks, but we were unable to access the Sitecore backend!

Only GetActiceUser() is called when signing in to the Sitecore backend, the other methods not at all. So, on recommendation of Sitecore Support, we just call base when we are on the Sitecore backend.

Here is the custom provider:

using System;
using System.Linq;
using System.Web;
using Sitecore;
using Sitecore.Security.Accounts;
using Sitecore.Security.Domains;

namespace Custom.Security.Authentication
{
    public class CustomFormsAuthenticationProvider : Sitecore.Security.Authentication.FormsAuthenticationProvider
    {
        private const string LoggedInSessionKey = "LoggedIn";
        private static readonly string[] SitesToSkip = new[] { "shell", "login", "admin" };

        public override bool Login(User user)
        {
            if (!base.Login(user))
            {
                return false;
            }

            if (IsValidSite())
            {
                HttpContext.Current.Session[LoggedInSessionKey] = true;
            }

            return true;
        }

        public override bool Login(string userName, bool persistent)
        {
            if (!base.Login(userName, persistent))
            {
                return false;
            }

            if (IsValidSite())
            {
                HttpContext.Current.Session[LoggedInSessionKey] = true;
            }

            return true;
        }

        public override bool Login(string userName, string password, bool persistent)
        {
            if (!base.Login(userName, password, persistent))
            {
                return false;
            }

            if (IsValidSite())
            {
                HttpContext.Current.Session[LoggedInSessionKey] = true;
            }

            return true;
        }

        public override void Logout()
        {
            if (HttpContext.Current.Session != null && HttpContext.Current.Session[LoggedInSessionKey] != null)
            {
                HttpContext.Current.Session.Remove(LoggedInSessionKey);
            }

            base.Logout();
        }

        public override User GetActiveUser()
        {
            if (!IsValidSite() || IsMarkedAsLoggedInOrNoSession())
            {
                return base.GetActiveUser();
            }

            return Context.Domain.GetAnonymousUser() ?? Domain.GetDefaultAnonymousUser();
        }

        private static bool IsValidSite()
        {
            return !SitesToSkip.Contains(Sitecore.Context.GetSiteName(), StringComparer.OrdinalIgnoreCase);
        }

        public static bool IsMarkedAsLoggedInOrNoSession()
        {
            return HttpContext.Current == null
                || HttpContext.Current.Session == null
                || HttpContext.Current.Session[LoggedInSessionKey] != null;
        }
    }
}

Don't forget to let Sitecore know about your custom provider. Change web.config as follows:

<authentication defaultProvider="forms">
  <providers>
    <clear />
    <add name="forms" type="Custom.Security.Authentication.CustomFormsAuthenticationProvider, Custom" />
  </providers>
</authentication>

All this is wrapped up in a (personal) blog post on preventing cookie replay attacks in Sitecore.

| improve this answer | |
  • 1
    Fantastic answer and great tie in to your blog. Love it! – Pete Navarra Oct 28 '16 at 5:36
  • The config changes apply to the sitecore/authentication section of the config files (not necessarily in web.config), not system.web/authentication. – molnarm Jun 5 at 8:43
1

I would abandon the session if the variable is not present or false. Sitecore should do the rest. You can also explicitly logout the current user before abandoning the session.

| improve this answer | |
  • Where and when exactly should we validate the session variable? Keep in mind that we opt for a (reusable) solution that is transparant for the developers, i.e. in some provider, pipeline or event. – Thomas D Oct 20 '16 at 9:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.