Scott Hanselman

An IP Address Blocking HttpModule for ASP.NET in 9 minutes

December 15, 2004 Comment on this post [11] Posted in ASP.NET | HttpModule | Bugs
Sponsored By

I'm sure this has been done before, but it was faster to write it than to google for it. There's some IP Addresses that have been bothering me and I don't have access to a firewall or IIS at my ISP, so...

I can upload a text file called blockedips.txt to my site and the changes happen immediately.

    9 namespace YourModuleNameHere
   10 {
   11     public class IPBlackList : IHttpModule
   12     {
   13         private EventHandler onBeginRequest;
   14 
   15         public IPBlackList()
   16         {
   17             onBeginRequest = new EventHandler(this.HandleBeginRequest);
   18         }
   19 
   20         void IHttpModule.Dispose()
   21         {
   22         }
   23 
   24         void IHttpModule.Init(HttpApplication context)
   25         {
   26             context.BeginRequest += onBeginRequest;
   27         }
   28 
   29         const string BLOCKEDIPSKEY = "blockedips";
   30         const string BLOCKEDIPSFILE = "SiteConfig/blockedips.config";
   31 
   32         public static StringDictionary GetBlockedIPs(HttpContext context)
   33         {
   34             StringDictionary ips = (StringDictionary)context.Cache[BLOCKEDIPSKEY ];
   35             if (ips == null)
   36             {
   37                 ips = GetBlockedIPs(GetBlockedIPsFilePathFromCurrentContext(context));
   38                 context.Cache.Insert(BLOCKEDIPSKEY , ips, new CacheDependency(GetBlockedIPsFilePathFromCurrentContext(context)));
   39             }
   40             return ips;
   41         }
   42 
   43         private static string BlockedIPFileName = null;
   44         private static object blockedIPFileNameObject = new object();
   45         public static string GetBlockedIPsFilePathFromCurrentContext(HttpContext context)
   46         {
   47             if (BlockedIPFileName != null)
   48                 return BlockedIPFileName;
   49             lock(blockedIPFileNameObject)
   50             {
   51                 if (BlockedIPFileName == null)
   52                 {
   53                     BlockedIPFileName = context.Server.MapPath(BLOCKEDIPSFILE);
   54                 }
   55             }
   56             return BlockedIPFileName;
   57         }
   58 
   59         public static StringDictionary GetBlockedIPs(string configPath)
   60         {
   61             StringDictionary retval = new StringDictionary();
   62             using (StreamReader sr = new StreamReader(configPath))
   63             {
   64                 String line;
   65                 while ((line = sr.ReadLine()) != null)
   66                 {
   67                     line = line.Trim();
   68                     if (line.Length != 0)
   69                     {
   70                         retval.Add(line, null);
   71                     }
   72                 }
   73             }
   74             return retval;
   75         }
   76 
   77         private void HandleBeginRequest( object sender, EventArgs evargs )
   78         {
   79             HttpApplication app = sender as HttpApplication;
   80 
   81             if ( app != null )
   82             {
   83                 string IPAddr = app.Context.Request.ServerVariables["REMOTE_ADDR"];
   84                 if (IPAddr == null || IPAddr.Length == 0)
   85                 {
   86                     return;
   87                 }
   88 
   89                 StringDictionary badIPs = GetBlockedIPs(app.Context);
   90                 if (badIPs != null && badIPs.ContainsKey(IPAddr))
   91                 {
   92                     app.Context.Response.StatusCode = 404;
   93                     app.Context.Response.SuppressContent = true;
   94                     app.Context.Response.End();
   95                     return;
   96                 }
   97             }
   98         }
   99     }
  100 }

And in your web.config:

   42 <system.web>
   43    <httpModules>
   44         <add type="YourModuleNameHere.IPBlackList, YourAssemblyNameHere"
   45             name="IPBlackList" />
   46    </httpModules>
   47 </system.web>

No warrenty, express or implied. If it sucks or has bugs/security holes, let me know as it's 9 minutes work.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
December 15, 2004 10:03
Thats cool Scott! We have something similar running under IIS as an ISAPI filter that we use to allow/disallow access to certain urls.

As someone who keeps meaning to get more into ASP.NET and doesn't seem to ever find the time, it's interesting to see this kind of solution, along with the cache use.

Nice use of 9 minutes!
Ian
December 15, 2004 19:10
FYI: Might not matter since you may be using the dotted-decimal IP representation, but the StringDictionary will force lowercase on all your keys. This may cause the ContainsKey method to return false unless you also lowercase the argument to ContainsKey.
December 15, 2004 19:12
Doh! My bad...ContainsKey will lowercase the argument for you...sorry about that.
December 15, 2004 22:53
Thanks for the code Scott. This will come in handy.
December 17, 2004 22:07
I read some guidance once that suggested using HttpApplication.CompleteRequest() instead of HttpResponse.End() as you do in line 94.

However, I don't think I fully understood the reason, or when it was appropriate to use one over the other, so I'm not saying your code is "wrong".

The only difference I can tell is that Response.End raises a ThreadAbortException, while CompleteRequest() just ends the request. And raising an exception is more expensive than not, so maybe the guidance was performance related?

Anyone have any input on this?
December 18, 2004 1:26
Good info....interesting....CompleteRequest is on the Application, and sets an internal bool:

public void CompleteRequest()
{
this._requestCompleted = true;
}


and HttpResponse.End does this (notice that it calls CompleteRequest() also...


public void End()
{
if (this._context.IsInCancellablePeriod)
{
InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{
this.Flush();
this._ended = true;
this._context.ApplicationInstance.CompleteRequest();
}
}


I'll do some testing and look at the differences...thanks!

Scott
December 19, 2004 23:25
What would it take to turn this into a module people can drop into any IIS website and turn on?

What would it take to make it reference some DNS based RBL?

Would that DNS RBL end comment spam for it's users?
December 20, 2004 0:01
1. It already is a module you can drop into any (ASP.NET) IIS website. You're looking at it.
2. Show me a blacklist to point to.
3. Probably not.

December 20, 2004 4:13
Sorbs has lists for open http proxies and open socks proxies: http://www.dnsbl.nl.sorbs.net/using.shtml
January 16, 2005 22:55
Great code Scott. I've already had it in use for a couple weeks now. I was going through the code changing a couple things for a new implementation when I noticed tha common oversight at line 67:

line.Trim();
if (line.Length != 0)
{
retval.Add(line, null);
}

Just an explanation for anyone who does not know, since I brought it up. Trim() only returns a new trimmed string, it does not operate on the variable itself. You need to assign the Trim() method's return value to a string in order to get the trimmed value. If the 'line' variable only contained four spaces, it's tested length would still be 4. To trim the string the code should be:

line = line.Trim();
January 17, 2005 0:42
You're totally right...brain fart. Thanks!

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.