Prevent Denial of Service (DOS) attacks in your web application
Web services are the most attractive target for hackers because
even a pre-school hacker can bring down a server by repeatedly
calling a web service which does expensive work. Ajax Start Pages
like
Pageflakes are the best
target for such DOS attack because if you just visit the homepage
repeatedly without preserving cookie, every hit is producing a
brand new user, new page setup, new widgets and what not. The first
visit experience is the most expensive one. Nonetheless, it’s
the easiest one to exploit and bring down the site. You can try
this yourself. Just write a simple code like this:
1: for( int i = 0; i < 100000; i ++ )
2: {
3: WebClient client = new WebClient();
4: client.DownloadString("http://www.pageflakes.com/default.aspx");
5: }
In your great surprise, you will notice that, after a couple of
call, you don't get valid response. It’s not that you have
succeeded in bringing down the server. It’s that your
requests are being rejected. You are happy that you no longer get
any service, thus you achieve Denial of Service (for yourself). I
am happy to Deny You of Service (DYOS).
The trick I have in my sleeve is an inexpensive way to remember
how many requests are coming from a particular IP. When the number
of request exceeds the threshold, deny further request for some
duartion. The idea is to remember caller’s IP in Asp.net
Cache and maintain a count of request per IP. When the count
exceeds a predefined limit, reject further request for some
specific duration like 10 mins. After 10 mins, again allow requests
from that IP.
I have a class named ActionValidator which maintains a count of
specific actions like First Visit, Revisit, Asynchrnous postbacks,
Add New widget, Add New Page etc. It checks whether the count for
such specific action for a specific IP exceeds the threshold value
or not.
1: public static class ActionValidator
2: {
3: private const int DURATION = 10; // 10 min period
4:
5: public enum ActionTypeEnum
6: {
7: FirstVisit = 100, // The most expensive one, choose the valu wisely.
8: ReVisit = 1000, // Welcome to revisit as many times as use likes
9: Postback = 5000, // Not must of a problem for us
10: AddNewWidget = 100,
11: AddNewPage = 100,
12: }
The enumeration contains the type of actions to check for and
their threshold value for a specific duration – 10 mins.
A static method named
IsValid does the check. It returns true if the request
limit is not passed, false if the request needs to be denied. Once
you get false, you can call
Request.End() and prevent Asp.net from proceeding further.
You can also switch to a page which shows “Congratulations!
You have succeeded in Denial of Service Attack.”
1:
public static bool IsValid( ActionTypeEnum actionType )
2: {
3: HttpContext context = HttpContext.Current;
4:
if( context.Request.Browser.Crawler ) return false;
5:
6: string key = actionType.ToString() + context.Request.UserHostAddress;
7:
8: HitInfo hit = (HitInfo)(context.Cache[key] ?? new HitInfo());
9:
10: if( hit.Hits > (int)actionType ) return false;
11: else hit.Hits ++;
12:
13: if( hit.Hits == 1 )
14: context.Cache.Add(key, hit, null, DateTime.Now.AddMinutes(DURATION),
15: System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
16:
17: return true;
18: }
The cache key is built with a combination of action type and
client IP address. First it checks if there’s any entry for
the action and the client IP in Cache or not. If not, start the
count and store remember the count for the IP in cache for the
specific duration. The absolute expiration on cache item ensures
after the duration, the cache item will be cleared and the count
will restart. When there’s already an entry in the cache, get
the last hit count, and check if the limit is exceeded or not. If
not exceeded, increase the counter. There is no need to store the
updated value in the cache again by doing: Cache[url]=hit; because
the hit object is by reference and changing it means it gets
changed in the cache as well. In fact, if you do put it again in
the cache, the cache expiration counter will restart and fail the
logic of restarting count after specific duration.
The usage is very simple:
1:
protected override void OnInit(EventArgs e)
2: {
3: base.OnInit(e);
4:
5: // Check if revisit is valid or not
6: if( !base.IsPostBack )
7: {
8: // Block cookie less visit attempts
9: if( Profile.IsFirstVisit )
10: {
11: if( !ActionValidator.IsValid(ActionValidator.ActionTypeEnum.FirstVisit) Response.End();
12: }
13: else
14: {
15: if( !ActionValidator.IsValid(ActionValidator.ActionTypeEnum.ReVisit) ) Response.End();
16: }
17: }
18: else
19: {
20: // Limit number of postbacks
21: if( !ActionValidator.IsValid(ActionValidator.ActionTypeEnum.Postback) Response.End();
22: }
23: }
Here I am checking specific scenario like First Visit, re-visit,
postbacks etc.
Of course you can put in some Cisco firewall and prevent DOS
attack. You will get guaranty from your hosting provider that their
entire network is immune to DOS and DDOS (Distributed DOS) attacks.
What they guaranty is network level attack like TCP SYN attacks or
malformed packet floods etc. There is no way they can analyze the
packet and find out a particular IP is trying to load the site too
many times without supporting cookie or trying to add too many
widgets. These are called application level DOS attack which
hardware cannot prevent. It must be implemented in your own
code.
There are very few websites out their which take such precaution
for application level DOS attacks. Thus it’s quite easy to
make servers go mad by writing a simple loop and hitting expensive
pages or web services continuously from your home broadband
connection. I hope this small but effective class will help
you implement DOS attack in your own web applications.
Update
Here's the code of the full ActionValidator class:
// Copyright (c) Omar AL Zabir. All rights reserved.
// For continued development and updates, visit http://msmvps.com/omar
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// Summary description for ActionValidator
/// </summary>
namespace Dropthings.Web.Util
{
public static class ActionValidator
{
private const int DURATION = 10; // 10 min period
/*
* Type of actions and their maximum value per period
*
*/
public enum ActionTypeEnum
{
None = 0,
FirstVisit = 100, // The most expensive one, choose the value wisely.
Revisit = 1000, // Welcome to revisit as many times as user likes
Postback = 5000, // Not must of a problem for us
AddNewWidget = 100,
AddNewPage = 100,
}
private class HitInfo
{
public int Hits;
private DateTime _ExpiresAt = DateTime.Now.AddMinutes(DURATION);
public DateTime ExpiresAt { get { return _ExpiresAt; } set { _ExpiresAt = value; } }
}
public static bool IsValid( ActionTypeEnum actionType )
{
HttpContext context = HttpContext.Current;
if( context.Request.Browser.Crawler ) return false;
string key = actionType.ToString() + context.Request.UserHostAddress;
HitInfo hit = (HitInfo)(context.Cache[key] ?? new HitInfo());
if( hit.Hits > (int)actionType ) return false;
else hit.Hits ++;
if( hit.Hits == 1 )
context.Cache.Add(key, hit, null, DateTime.Now.AddMinutes(DURATION),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
return true;
}
}
}