URL Activity Tampering and how to prevent it in MVC

No web applications are developed without concerning the security issues. But it is not sure whether the security measures are implemented correctly and the applications are well protected. We could see a lot of web applications query string parameters in plain string which are easy to manipulate and way to escalate restricted resources. Even the URL parameters are encrypted there is still a chance of a bruteforce attack by manipulating the encrypted hash and it yields when the database is large where the chance of matching a random hash is high. So it is clear the importance to protect the URL in the web application is necessary for all programming platforms. Here, this is discussed with reference to ASP.NET MVC and the utility class is presented.

.NET Framework has a lot of improved methods for security and the MVC framework introduced new security methods like AntiForgeryToken that I discussed in a previous article Preventing Cross Site Request Forgery. Let us have a brief look at the security methods in general practice.

  • Authorize attribute: This is useful to authorize a user or role to access any resource in the application after authenticated. This helps to have User and Role level protections.
  • Http Referrer Check: This is a general method not confined to .NET. This is to prevent an URL request which is not from the site, but from an external link or the link directly executed at the browser navigation
  • Anti Forgery Token: This is a powerful option to prevent any hidden field manipulation while form posting and prevents Cross-Site Request Forgery
  • HTML Encoding: It is advised to encode all user inputs to prevent Cross Site Scripting attack/ XSS attack etc.,
  • Protection against SQL injections
  • Encryption of Query string parameters. This is good way to prevent manipulation of query string parameters. But this is not a complete protection, still vulnerable for a brute force attack.

Even though all the above methods are available, .NET framework doesn’t provide an inbuilt functionality for protecting the URL parameters. Its URL authorization technique protects the whole URL, but not parts of the URL which are still vulnerable for manipulation. This piece of coding here helps to easily implement all these security methods with special importance to protecting the URL parameters. Creating a URL hash is not new but yet it is not a common practice in the industry though it is an important concern. So it would be important to share a piece of code for the easy implementation of this method in ASP.NET MVC.

The basic idea is simple, encrypting the URL parameters along with controller, action names to a hash and later it will be passed to server as a URL parameter. The hash will be verified at the server side for its validity by recomputing the hash and compared with the submitted hash. This hash is dynamic and specific to a session. So even though your URL cached in the man-in-the middle attack, it will not work in other sessions. Basically a utility class extends the HTML helper ActionLink class. So we can use Html.ActionLink method as such in the MVC implementation, in addition you only need to pass a boolean argument as a last argument to get the protected URL. It is pretty easy to use this coding. Examples are shown below.

<%=Html.ActionLink("link Test 3", "HandleLink", new { id = 200 }, true)%>

You’ll notice the extra boolean parameter at the end to enable encryption.

The ExtendedHtmlActionLinkHelper looks like this:

namespace ExtendedHtmlActionLinkHelper
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Mvc;
    using System.Web.Routing;

    /// <summary>
    /// This class is an extension for the Html Helper
    /// </summary>
    public static class ActionLinkExtensions
    {
        // This password is included in the hash. If needed it can be changed.
        private static string tokenPassword = "@#123%#";

        public static string TokenPassword
        {
            get { return tokenPassword; }

            set { tokenPassword = value; }
        }

        /// <summary>
        /// This is the extended method of Html.ActionLink.This class has the extended methods for the 10 overloads of this method
        /// All the overloaded method applys the same logic excepts the parameters passed in
        /// </summary>
        /// <param name="helper">The helper.</param>
        /// <param name="linkText">The link text.</param>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="generateToken">if set to <c>true</c> [generate token].</param>
        /// <returns></returns>
        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = new RouteValueDictionary();
            if (generateToken)
            {
                // Call the generateUrlToken method which create the hash
                var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd, TokenPassword);

                // The hash is added to the route value dictionary
                rvd.Add("urltoken", token);
            }

            // the link is formed by using the GenerateLink method.
            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, null, rvd, null);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, object routeValues, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = new RouteValueDictionary(routeValues);
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd, TokenPassword);
                rvd.Add("urltoken", token);
            }

            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, null, rvd, null);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, RouteValueDictionary routeValues, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = routeValues;
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd, TokenPassword);
                rvd.Add("urltoken", token);
            }

            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, null, rvd, null);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = new RouteValueDictionary();
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(controllerName, actionName, rvd, TokenPassword);
                rvd.Add("urltoken", token);
            }

            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, controllerName, rvd, null);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, object routeValues, object htmlAttributes, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = new RouteValueDictionary(routeValues);
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd, TokenPassword);
                rvd.Add("urltoken", token);
            }

            var attrib = GetDictionaryFromObject(htmlAttributes);
            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, null, rvd, attrib);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, routeValues, TokenPassword);
                routeValues.Add("urltoken", token);
            }

            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, null, routeValues, htmlAttributes);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = new RouteValueDictionary(routeValues);
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(controllerName, actionName, rvd, TokenPassword);
                rvd.Add("urltoken", token);
            }

            var attrib = GetDictionaryFromObject(htmlAttributes);
            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, controllerName, rvd, attrib);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(controllerName, actionName, routeValues, TokenPassword);
                routeValues.Add("urltoken", token);
            }

            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, controllerName, routeValues, htmlAttributes);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, object routeValues, object htmlAttributes, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            var rvd = new RouteValueDictionary(routeValues);
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(controllerName, actionName, rvd, TokenPassword);
                rvd.Add("urltoken", token);
            }

            var attrib = GetDictionaryFromObject(htmlAttributes);
            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, controllerName, protocol, hostName, fragment, rvd, attrib);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        public static MvcHtmlString ActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool generateToken)
        {
            var rc = helper.ViewContext.RequestContext;
            var routeCollection = helper.RouteCollection;
            if (generateToken)
            {
                var token = TokenUtility.GenerateUrlToken(controllerName, actionName, routeValues, TokenPassword);
                routeValues.Add("urltoken", token);
            }

            var link = HtmlHelper.GenerateLink(rc, routeCollection, linkText, null, actionName, controllerName, protocol, hostName, fragment, routeValues, htmlAttributes);
            var mvcHtmlString = MvcHtmlString.Create(link);
            return mvcHtmlString;
        }

        private static IDictionary<string, object> GetDictionaryFromObject(object sourceObject)
        {
            var objType = sourceObject.GetType();

            return objType.GetProperties().ToDictionary(prop => prop.Name, prop => prop.GetValue(sourceObject, null));
        }
    }
}

It is also worth generating the UrlHelper.Action too should this be required

using System.Web.Mvc;
using System.Web.Routing;

public static class ActionExtensions
{
    public static MvcHtmlString Action(this UrlHelper helper, string actionName, bool generateToken)
    {
        var rvd = new RouteValueDictionary();
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, rvd);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }

    public static MvcHtmlString Action(this UrlHelper helper, string actionName, object routeValues, bool generateToken)
    {
        if(routeValues== null)
        {
            routeValues = new RouteValueDictionary();
        }

        var rvd = (RouteValueDictionary)routeValues;
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, rvd);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }

    public static MvcHtmlString Action(this UrlHelper helper, string actionName, RouteValueDictionary routeValues, bool generateToken)
    {
        if (routeValues == null)
        {
            routeValues = new RouteValueDictionary();
        }

        var rvd = routeValues;
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, rvd);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }

    public static MvcHtmlString Action(this UrlHelper helper, string actionName, string controllerName, bool generateToken)
    {
        var rvd = new RouteValueDictionary();
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, controllerName, rvd);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }


    public static MvcHtmlString Action(this UrlHelper helper, string actionName, string controllerName, object routeValues, bool generateToken)
    {
        if (routeValues == null)
        {
            routeValues = new RouteValueDictionary();
        }

        var rvd = (RouteValueDictionary)routeValues;
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, controllerName, rvd);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }


    public static MvcHtmlString Action(this UrlHelper helper, string actionName, string controllerName, RouteValueDictionary routeValues, bool generateToken)
    {
        if (routeValues == null)
        {
            routeValues = new RouteValueDictionary();
        }

        var rvd = routeValues;
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, controllerName, rvd);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }


    public static MvcHtmlString Action(this UrlHelper helper, string actionName, string controllerName, object routeValues, string protocol, bool generateToken)
    {
        if (routeValues == null)
        {
            routeValues = new RouteValueDictionary();
        }

        var rvd = (RouteValueDictionary)routeValues;
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, controllerName, rvd, protocol);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }

    public static MvcHtmlString Action(this UrlHelper helper, string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName, bool generateToken)
    {
        if (routeValues == null)
        {
            routeValues = new RouteValueDictionary();
        }

        var rvd = routeValues;
        if (generateToken)
        {
            // Call the generateUrlToken method which create the hash
            var token = TokenUtility.GenerateUrlToken(string.Empty, actionName, rvd);

            // The hash is added to the route value dictionary
            rvd.Add("urltoken", token);
        }

        // the link is formed by using the GenerateLink method.
        var link = new UrlHelper(helper.RequestContext).Action(actionName, controllerName, rvd, protocol, hostName);
        var mvcHtmlString = MvcHtmlString.Create(link);
        return mvcHtmlString;
    }
        
}Getting a protected URL in the view is easy as just passing a boolean parameter. Now let us see how to validate the URL when submitted to controller. It is even simpler. Just add this attribute to the controller method.
[IsValidURLRequest]

This attribute has taken care of the simple authorize check (user name, password verification), check for http referrer and protect the URL parameters.

Now for the TokenUtility that performs most of the work for creating the URL Hash.

using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Routing;

/// <summary>
/// This is a static helper class which creates the URL Hash
/// </summary>
public static class TokenUtility
{
    public static string GenerateUrlToken(string controllerName, string actionName, RouteValueDictionary argumentParams, string password)
    {
        //// The URL hash is dynamic by assign a dynamic key in each session. So
        //// eventhough your URL is stolen, it will not work in other session
        if (HttpContext.Current.Session["url_dynamickey"] == null)
        {
            HttpContext.Current.Session["url_dynamickey"] = RandomString();
        }

        // The salt include the dynamic session key and valid for an hour.
        var salt = HttpContext.Current.Session["url_dynamickey"] + DateTime.Now.ToShortDateString() + " " + DateTime.Now.Hour;

        // generating the partial url
        var stringToToken = controllerName + "/" + actionName + "/";
        stringToToken = argumentParams.Where(item => item.Key != "controller" && item.Key != "action" && item.Key != "urltoken").Aggregate(stringToToken, (current, item) => current + (item.Value));

        // Converting the salt in to a byte array
        var saltValueBytes = System.Text.Encoding.ASCII.GetBytes(salt);

        // Encrypt the salt bytes with the password
        var key = new Rfc2898DeriveBytes(password, saltValueBytes);

        // get the key bytes from the above process
        var secretKey = key.GetBytes(16);

        // generate the hash
        var tokenHash = new HMACSHA1(secretKey);
        tokenHash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(stringToToken));

        // convert the hash to a base64string
        var token = Convert.ToBase64String(tokenHash.Hash).Replace("/", "_");

        return token;
    }

    /// <summary>
    /// This validates the token
    /// </summary>
    /// <param name="token">The token.</param>
    /// <param name="controllerName">Name of the controller.</param>
    /// <param name="actionName">Name of the action.</param>
    /// <param name="argumentParams">The argument params.</param>
    /// <param name="password">The password.</param>
    /// <returns></returns>
    public static bool ValidateToken(string token, string controllerName, string actionName, RouteValueDictionary argumentParams, string password)
    {
        // compute the token for the currrent parameter
        var computedToken = GenerateUrlToken(controllerName, actionName, argumentParams, password);

        // compare with the submitted token
        if (computedToken != token)
        {
            computedToken = GenerateUrlToken(string.Empty, actionName, argumentParams, password);
        }
        else { return true; }

        return computedToken == token;
    }

    /// <summary>
    /// It validates the token, where all the parameters passed as a RouteValueDictionary
    /// </summary>
    /// <param name="requestUrlParts">The request URL parts.</param>
    /// <param name="password">The password.</param>
    /// <param name="token">the Url encrypted token to be used to validate against</param>
    /// <returns></returns>
    public static bool ValidateToken(RouteValueDictionary requestUrlParts, string password, string token)
    {
        // get the parameters
        string controllerName;
        try
        {
            controllerName = Convert.ToString(requestUrlParts["controller"]);
        }
        catch (Exception)
        {
            controllerName = string.Empty;
        }

        var actionName = Convert.ToString(requestUrlParts["action"]);
        //var token = Convert.ToString(requestUrlParts["urltoken"]);

        // Compute a new hash
        var computedToken = GenerateUrlToken(controllerName, actionName, requestUrlParts, password);

        // compare with the submited hash
        if (computedToken != token)
        {
            computedToken = GenerateUrlToken(string.Empty, actionName, requestUrlParts, password);
        }
        else
        {
            return true;
        }

        return computedToken == token;
    }

    /// <summary>
    /// This method create the random dynamic key for the session
    /// </summary>
    /// <returns></returns>
    private static string RandomString()
    {
        var builder = new StringBuilder();
        var random = new Random();
        for (var i = 0; i < 8; i++)
        {
            var ch = Convert.ToChar(Convert.ToInt32(Math.Floor((26 * random.NextDouble()) + 65)));
            builder.Append(ch);
        }

        return builder.ToString();
    }
}

 

ExtendedHtmlActionLinkHelper (3).zip (277.13 kb)

Original Source

  • Jeremy Price

    Hi. The ‘ExtendedHtmlActionLinkHelper (3).zip’ download link is broken.