Security issues on version 3.50

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
9 years ago
Hi,

I've done some security fixes on the version 3.50.
Now my question is whether this changes can be included in the future releases of nopCommerce.

Issue: Preventing open redirection attacks
File: /Presentation/Nop.Web/Administration/Controllers/CommonController.cs
Code:
 public ActionResult SetLanguage(int langid, string returnUrl = "")
        {
            var language = _languageService.GetLanguageById(langid);
            if (language != null)
            {
                _workContext.WorkingLanguage = language;
            }

            //home page
            // Preventing open redirection attacks
            if (String.IsNullOrEmpty(returnUrl) || !Url.IsLocalUrl(returnUrl))
                returnUrl = Url.Action("Index", "Home", new { area = "Admin" });
            return Redirect(returnUrl);
        }


Issue:  Broken Authentication and Session Management
File: /Libraries/Nop.Services/Authentication/FormsAuthenticationService.cs
Description: To prevent cookie replay attacks, I generate a random guid and I store it in the user data section and in the session
Code:
public partial class FormsAuthenticationService : IAuthenticationService
    {
        private readonly HttpContextBase _httpContext;
        private readonly ICustomerService _customerService;
        private readonly CustomerSettings _customerSettings;
        private readonly TimeSpan _expirationTimeSpan;

        private Customer _cachedCustomer;
        
        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="httpContext">HTTP context</param>
        /// <param name="customerService">Customer service</param>
        /// <param name="customerSettings">Customer settings</param>
        public PostDocAuthenticationService(HttpContextBase httpContext,
            ICustomerService customerService, CustomerSettings customerSettings)
        {
            this._httpContext = httpContext;
            this._customerService = customerService;
            this._customerSettings = customerSettings;
            this._expirationTimeSpan = FormsAuthentication.Timeout;
        }


        public virtual void SignIn(Customer customer, bool createPersistentCookie)
        {
            var now = DateTime.UtcNow.ToLocalTime();

            // To prevent cookie replay attacks, I generate a random guid and I store it in the user data section and in the session
            var userGuid = Guid.NewGuid();

            var ticket = new FormsAuthenticationTicket(
                1 /*version*/,
                _customerSettings.UsernamesEnabled ? customer.Username : customer.Email,
                now,
                now.Add(_expirationTimeSpan),
                createPersistentCookie,
                _customerSettings.UsernamesEnabled ? customer.Username + "_" + userGuid : customer.Email + "_" + userGuid,
                FormsAuthentication.FormsCookiePath);

            var encryptedTicket = FormsAuthentication.Encrypt(ticket);

            var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            cookie.HttpOnly = true;
            if (ticket.IsPersistent)
            {
                cookie.Expires = ticket.Expiration;
            }
            cookie.Secure = FormsAuthentication.RequireSSL;
            cookie.Path = FormsAuthentication.FormsCookiePath;
            if (FormsAuthentication.CookieDomain != null)
            {
                cookie.Domain = FormsAuthentication.CookieDomain;
            }

            _httpContext.Response.Cookies.Add(cookie);
            _cachedCustomer = customer;
            
            // Store in session the random guid to prevent  cookie replay attacks
            if (HttpContext.Current != null && HttpContext.Current.Session != null)
            
            {
                HttpContext.Current.Session["authID"] = userGuid;
            }

            if (_httpContext.Session != null)
            {
                _httpContext.Session["authID"] = userGuid;
            }
        }

        public virtual void SignOut()
        {
            _cachedCustomer = null;

            if (_httpContext != null && _httpContext.Session != null)
                _httpContext.Session.Abandon();

            FormsAuthentication.SignOut();
        }

        public virtual Customer GetAuthenticatedCustomer()
        {
            if (_cachedCustomer != null)
                return _cachedCustomer;

            if (_httpContext == null ||
                _httpContext.Request == null ||
                !_httpContext.Request.IsAuthenticated ||
                !(_httpContext.User.Identity is FormsIdentity))
            {
                return null;
            }

            var formsIdentity = (FormsIdentity)_httpContext.User.Identity;
            var customer = GetAuthenticatedCustomerFromTicket(formsIdentity.Ticket);
            if (customer != null && customer.Active && !customer.Deleted && customer.IsRegistered())
            {
                _cachedCustomer = customer;
            }
            
            return _cachedCustomer;
        }

        public virtual Customer GetAuthenticatedCustomerFromTicket(FormsAuthenticationTicket ticket)
        {
            if (ticket == null)
                throw new ArgumentNullException("ticket");

            var usernameOrEmail = ticket.UserData;
            
            // User data section must contain the random guid generated during the login
            if (!usernameOrEmail.Contains("_"))
            {
                return null;
            }

            usernameOrEmail = usernameOrEmail.Split('_')[0];

            if (String.IsNullOrWhiteSpace(usernameOrEmail))
            {
                return null;
            }
            
            var customer = _customerSettings.UsernamesEnabled
                ? _customerService.GetCustomerByUsername(usernameOrEmail)
                : _customerService.GetCustomerByEmail(usernameOrEmail);
            
            return customer;
        }
    }


File: /Presentation/Nop.Web.Framework/Controllers/AdminAuthorizeAttribute.cs
Description: Check  the random guid saved in the session
Code:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited=true, AllowMultiple=true)]
    public class AdminAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
    {
        private readonly bool _dontValidate;
        
        public AdminAuthorizeAttribute()
            : this(false)
        {
        }

        public AdminAuthorizeAttribute(bool dontValidate)
        {
            this._dontValidate = dontValidate;
        }

        private void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }

        private IEnumerable<AdminAuthorizeAttribute> GetAdminAuthorizeAttributes(ActionDescriptor descriptor)
        {
            return descriptor.GetCustomAttributes(typeof(AdminAuthorizeAttribute), true)
                .Concat(descriptor.ControllerDescriptor.GetCustomAttributes(typeof(AdminAuthorizeAttribute), true))
                .OfType<AdminAuthorizeAttribute>();
        }

        private bool IsAdminPageRequested(AuthorizationContext filterContext)
        {
            var adminAttributes = GetAdminAuthorizeAttributes(filterContext.ActionDescriptor);
            if (adminAttributes != null && adminAttributes.Any())
                return true;
            return false;
        }

        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (_dontValidate)
                return;

            if (filterContext == null)
                throw new ArgumentNullException("filterContext");

            if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
                throw new InvalidOperationException("You cannot use [AdminAuthorize] attribute when a child action cache is active");

            if (IsAdminPageRequested(filterContext))
            {
                if (!this.HasAdminAccess(filterContext))
                    this.HandleUnauthorizedRequest(filterContext);
            }
        }

        public virtual bool HasAdminAccess(AuthorizationContext filterContext)
        {
            var permissionService = EngineContext.Current.Resolve<IPermissionService>();
            
            if (HttpContext.Current != null && HttpContext.Current.User != null)
            {
                var formsIdentity = HttpContext.Current.User.Identity as FormsIdentity;

                if (formsIdentity == null)
                {
                    return false;
                }

                var ticket = formsIdentity.Ticket;

                if (ticket == null)
                {
                    return false;
                }

                var usernameOrEmail = ticket.UserData;

                if (!usernameOrEmail.Contains("_"))
                {
                    return false;
                }

                if (HttpContext.Current.Session == null)
                {
                    return false;
                }
                if (HttpContext.Current.Session["authID"] == null)
                {
                    return false;
                }

                var authId = HttpContext.Current.Session["authID"].ToString();
                var cookieAuthId = usernameOrEmail.Split('_')[1];

                // Compare the random guids
                if (!authId.Equals(cookieAuthId))
                {
                    return false;
                }
            }


            bool result = permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel);
            return result;
        }
    }
9 years ago
About the "cookie replay attacks" nopcommerce 3.50 is using MVC 5, according to some articles MVC version 4.5 and earlier are vulnerable to this type of attack.
9 years ago
Hi,

Thanks a lot for suggestions. I've just created work items (here and here)
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.