Introduction
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.
Background
.NET Framework has a lot of improved methods for security and the MVC framework introduced new security methods like AntiForgeryToken
. 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 bar.
- 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.
Using the Code
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.
(Source code attached: it is developed in Visual Studio Express 2010, SQL Express. This development platform is freely available.)
Try the demo project or include the ActionLinkExtensions.cs from the demo project to your project.
The figure shows the files having the core coding and those can be reused in your projects.
Add the following lines in your view below the page declarations:
Note
- The boolean parameter passed in red color is the only additional parameter to get a protected URL, apart from the 10 overloaded parameters in the ASP MVC HTML.
ActionLink
method.
ExtendedHtmlActionLinkHelper
- It is the namespace of this example project and it will change in your implementations.
RouteValues()
and HtmlAttributes()
are test classes that have test parameters and test attribute for hyperlink respectively. It changes in your implementations.
The Controller Code
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.
However one more thing has to do with the routing for avoiding confusions with routes. Add the 'urltoken
' as the first parameter in the route in global.asax and all the controller methods have this as the first argument. This can be an optional parameter. Route mapping example follows.
routes.MapRoute(
"Default", "{controller}/{action}/{urltoken}/{id}", new { controller = "Home", action = "Index", urltoken = UrlParameter.Optional,
id = UrlParameter.Optional } );
The Model
This model code is completely reusable in your project. However, it depends on a controller named ErrorviewController
and two action methods DisplayURLError
and DisplayHttpReferrerError
which need to be available in your project namespace.
All the model code for generating and validating the secure URL are in the file ActionLinkExtensions.cs, namespace SecuredUrl.Links
in the demo project. This file can be included in any project and reused. Basically, it extends the HTML helper method 'ActionLink
'. Nothing much to discuss about how to extend the HTML helper method as much information is available about this in CodeProject. Let us discuss how the URL hash is formed. Have a look at the following code:
public static class TokenUtility
{
public static string generateUrlToken(string controllerName, string actionName,
RouteValueDictionary argumentParams, string password)
{
if (HttpContext.Current.Session["url_dynamickey"] == null)
{
HttpContext.Current.Session["url_dynamickey"] = RandomString();
}
string token = "";
//The salt include the dynamic session key and valid for an hour.
string salt = HttpContext.Current.Session["url_dynamickey"].ToString() +
DateTime.Now.ToShortDateString() + " " + DateTime.Now.Hour; ;
//generating the partial url
string stringToToken = controllerName + "/" + actionName + "/";
foreach (KeyValuePair<string, object> item in argumentParams)
{
if (item.Key != "controller" &&
item.Key != "action" && item.Key != "urltoken")
{
stringToToken += "/" + item.Value;
}
}
//Converting the salt in to a byte array
byte[] saltValueBytes = System.Text.Encoding.ASCII.GetBytes(salt);
//Encrypt the salt bytes with the password
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, saltValueBytes);
//get the key bytes from the above process
byte[] secretKey = key.GetBytes(16);
//generate the hash
HMACSHA1 tokenHash = new HMACSHA1(secretKey);
tokenHash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(stringToToken));
//convert the hash to a base64string
token = Convert.ToBase64String(tokenHash.Hash).Replace("/","_");
return token;
}
//This method validates the token
public static bool validateToken(string token,string controllerName,
string actionName, RouteValueDictionary argumentParams, string password)
{
//compute the token for the current parameter
string computedToken = generateUrlToken
(controllerName, actionName, argumentParams, password);
//compare with the submitted token
if (computedToken != token)
{
computedToken = generateUrlToken
("", actionName, argumentParams, password);
}
else { return true; }
if (computedToken != token)
{
return false;
}
else { return true; }
}
//It validates the token, where all the parameters passed as a RouteValueDictionary
public static bool validateToken
(RouteValueDictionary requestUrlParts, string password)
{
//get the parameters
string controllerName;
try
{
controllerName = Convert.ToString(requestUrlParts["controller"]);
}
catch (Exception e)
{
controllerName = "";
}
string actionName = Convert.ToString(requestUrlParts["action"]);
string token = Convert.ToString(requestUrlParts["urltoken"]);
//Compute a new hash
string computedToken = generateUrlToken
(controllerName, actionName, requestUrlParts, password);
//compare with the submitted hash
if (computedToken != token)
{
computedToken = generateUrlToken
("", actionName, requestUrlParts, password);
}
else { return true; }
if (computedToken != token)
{
return false;
}
else { return true; }
}
//This method create the random dynamic key for the session
private static string RandomString()
{
StringBuilder builder = new StringBuilder();
Random random = new Random();
char ch;
for (int i = 0; i < 8; i++)
{
ch = Convert.ToChar(Convert.ToInt32
(Math.Floor(26 * random.NextDouble() + 65)));
builder.Append(ch);
}
return builder.ToString();
}
}
//This is a attribute class which actually calls
//the validation and to be used with the controller
public class IsValidURLRequestAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// basic authorization check
base.OnAuthorization(filterContext);
if (filterContext.HttpContext != null)
{
//Http referrer check and do the redirection if error occurs
//It uses a controller named ErrorViewController and
//action named DisplayHttpReferrerError
//These controller and action need to be present
//in your project in the project name space
if (filterContext.HttpContext.Request.UrlReferrer == null)
{
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{ "langCode",
filterContext.RouteData.Values[ "langCode" ] },
{ "controller", "ErrorView" },
{ "action", "DisplayHttpReferrerError" },
{ "ReturnUrl", filterContext.HttpContext.Request.RawUrl },
});
}
}
/*Add code here to check the domain name the request come from*/
// The call for validation of URL hash and do the redirection if error occurs
//It uses a controller named ErrorViewController and action named DisplayURLError
//These controller and action need to be present in your project
//in the project name space
if (TokenUtility.validateToken
(filterContext.RequestContext.RouteData.Values,
ActionLinkExtensions.tokenPassword) == false)
{
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{ "langCode", filterContext.RouteData.Values[ "langCode" ] },
{ "controller", "ErrorView" },
{ "action", "DisplayURLError" },
{ "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
}
The above code is self explanatory. It creates a hash with a session depend key and attaches it to the URL. There is a validation attribute which validates for basic authorization, HTTPReferrer
and the URL hash.
Proof Of Concept
Run the demo application. Click any links in the page. If not logged in, then it will redirect to login page. For the next test, Log on (register yourself or use this login user: albin; pass: test123) and then copy the link into the navigation bar in a new tab in the browser, which is one of the ways to manipulate the parameter (forged) and execute the link. It will not pass for httpreferrer
check. As I notified in the code to check external links, add the code to check the domain from http referrer. Now inject this JavaScript.
(Once logged in, paste the following JavaScript in the navigation bar and hit enter key. It will create a "Test Me" link.).
javascript:alert(document.getElementById("testplace").innerHTML="<a href=
'<right click the link in the demo page-> copy link location than paste here.
Manipulate the parameter '>Test Me</a>");
Replace URL in the JavaScript with the one copied from the demo page and manipulate the parameter and then execute as said above.
For example, click the link "Link Test 2". It opens the next page and shows the result:
Right click the link and copy the link location:
Change the parameter.
javascript:alert(document.getElementById("testplace").innerHTML=
"<a href='http://localhost:59248/Home/handleLink/fjr2orS7nxTDjyetxJRb%2brJOd_k%3d/200>
Test Me</a>");
Now paste it in the nav bar and hit enter. Click ok on the alert message. You will get a "Test me" link as shown below:
Now click the "Test Me" link. The result will be
This passes the httpreferrer
check, but fails at URL hash check as the URL parameter is manipulated. If this checking is not done, the hacker might get access to other users data. This URL hash is session specific. Close your browser and re-run the demo. You can find out the hashes are changed.
Conclusively, this URL hash method provides a good security against URL parameter forgery. If you really want to make the hacker's life hell, use a combination of encrypted parameters and this URL hashing method.
Be secure!!
Points of Interest
I have written this small article to make use of this method in the common web applications, because I came across many sites which pass parameters in the URL unsecured. So I thought of making this method more common and I chose popular CodeProject to publish it. As a final word, if there are errors, please forgive me. In my next article, I will write about how this can be implemented in Ajax requests.