HOWTO: Protect non-.NET content
This is a frequently asked question - how to implement custom authentication on IIS. I am going to ramble a bit about the whole subject because this is a little pet peeve of mine...
Question:
Hi David,
I am an avid lurker on your Blog and our company posts some of your HOWTO's on out internal web. Thank you for all the good info.
We are running many websites in a server farm. All of the webs feature a mix of classic ASP and .NET content in C#. We are currently using C++ ISAPI filters to protect classic ASP content from miscreants. Authentication is done either by the ISAPI filters or by the .NET content. Credentials are checked by the ISAPI filters after authentication. We are migrating to IIS 6.0 on Windows Server 2003 but are not completely converted from IIS 5.0 on Windows 2000 Server.
Our problem is that the management of the ISAPI filters is taking too many resources. This is mainly due to the several versions of the filters (which were written in-house) and their configuration options. These options mostly have to do with DB connection strings and passthru mechanisms for content such as images, stylesheets and script files.
I would like to know if it is possible to protect clasic ASP and other non-.NET content soley with an IIS and/or .NET mechanism. Particularly IISWebFile, IISWebDirectory, IISWebVirtualDir, etc. in conjunction with web.config and machine.config settings. Can you help shed some light on this issue? Can you tell us of a way to protect web content without requiring ISAPI filters?
Answer:
Thanks for using my posts - it is exactly what I am hoping to do - provide digested information and insights that help you do something new/better with less time/money/resources. :-) But first, I have to take care of a little pet peeve of mine when it comes to this subject...
<soapbox>
By far the easiest and most secure mechanism to protect any sort of content on IIS is to use its built-in authentication protocols such as Basic/NTLM/Kerberos/Digest/Passport and built-in authorization model using NT ACLs on the NTFS FileSystem. I believe that it offers a good combination of simplicity to configure and use, secure, well-tested, and well-integrated. Active Directory adds scalable user identity management and intra-machine trust, which allows security concepts like constrained delegation to enable secure "forwarding" of user credentials to access remote resources. It is probably the most complete and commonly available security system which integrates across multiple servers seamlessly and secures both the user on the client and remote servers involved, and you get it when you choose Windows.
Unfortunately, most users do not understand security other than "I want it to be cheap, simple, and secure". Combined with the problem that Active Directory is easily misunderstood and hard to configure perfectly (security can be like that - usually, there are a million wrong ways to do something and only one secure, correct way), users often end up abandoning Active Directory for one bad reason or another. Instead, they choose to implement their own little simple, custom authentication/authorization solution that they understand and fit it into their environment... and get off to the wrong foot. Why? Because more likely than not, these solutions are either insecure in some fundamental way(s) from a security perspective (the user just does not know it... yet... since they rarely have the security/cryptography training), and even if the solution is secure, it basically ends up being a private implementation of the exact same publicly designed protocol that IIS already supports.
So, I question why anyone should ever re-invent the wheel when it comes to authentication/authorization, especially when your solution is usually less verified, less hardened, and probably built with less domain knowledege?
</soapbox>
... ok, now that I have gotten that off of my chest, let me answer your specific question.
Short Answer
If you are looking for a custom authentication solution which works with IIS5 and IIS6, applies to static files, ASP, and ASP.Net content, and does not involve ISAPI Filters, then there is no solution. The reason is simple:
- IIS only supports three extensibility mechanisms - CGI, ISAPI Extension, and ISAPI Filter (we changed this in IIS7 by adding a new API which allows both native and managed code to be used, but that is not the point of discussion here...)
- Prior to IIS6 only ISAPI Filter can "filter" a request without owning it
- Custom authentication/authorization is a request processing phase, not response generation - thus it requires the ability to "filter" a request without owning it
What this means is that:
- With an ISAPI Filter, custom authentication is always possible on any IIS version
- It is architecturally impossible to apply ASP.Net Forms authentication on ASP content on IIS5 because ASP.Net ends up owning generating the response for ASP content, which does not work (ASP.DLL should process ASP pages).
- It is architecturally possible on IIS6 to apply ASP.Net 2.0 Forms authentication on ASP content due to new IIS functionality allowing ISAPI Extension to "filter" a request without owning it, and ASP.Net 2.0 added support for this new functionality. To be clear, this does not work with ASP.Net 2.0 on IIS5 or ASP.Net 1.1 or IIS6 - they are both missing the necessary support.
Addendum
Ok, I am going to be brutally honest here. Please do not take it personally - I am just looking at the data and openly musing about it:
- I do not think your problem has anything to do with ISAPI Filter management costing too much resources
- I do not think that excluding solutions based on technology makes sense here
Why? Well, I think your real problem has to do with poorly designed ISAPI Filters that do not interact and integrate with each other cohesively to provide the comprehensive custom authentication/authorization story that you need. It is not surprising given my pet-peeve rant earlier - most people in this world should not be writing such systems. It is usually a one-off hack of some sort, and hacks beget hacks, etc... which gets ugly and complicated.
Also, deciding on solutions based on implementation technology makes no sense to me. Implementation technology does not build security; knowledgeable people do. If you cannot build the security system with ISAPI Filter, why would you be able to do it correctly with .Net or even re-leverage the built-in IIS mechanism? In other words, how does .Net or avoiding ISAPI Filter improve your situation? If you can give me the answer to that, I can probably tell you how to do it correctly with ISAPI Filter... and if you say that you have more experienced .Net developers than C++, then I will tell you that you are still doomed -- what you need are developers that understand security theory, and understanding of API is not a valid constraint.
In other words, I want to help you solve your fundamental problem. I do not believe your problem is caused by ISAPI Filters but rather by poor understanding and design, and IIS/.Net is not a solution to poor design. Learning how to build with better design (or buying implementations of better design) are the ways to solve your problem. I really do not understand what you desire in your ideal system nor what you want to do, so I cannot give any more concrete advice.
I can tell you that for a solution that has to cross over between IIS5 and IIS6 and has to protect static, ASP, and ASP.Net content, an ISAPI Filter is the only way. On IIS6 with ASP.Net 2.0, it is possible to impose ASP.Net Forms Authentication on any content type, but that requires an understanding of how IIS and ASP.Net interact when it comes to security. I have planned blog entries on this topic (rather, I need to finish up the series that I started a couple months ago...)
Once again, thanks for the encouragement.
//David
Comments
Anonymous
August 29, 2005
You say this about Active Directory:
"Active Directory is easily misunderstood and hard to configure perfectly."
And then you get onto a soapbox and complain about people not using Active Directory, wanting to do it "quickly, fastly, and cheaply," and either doing it wrong or doing it right but in a completely site-specific way.
Maybe the problem is that Active Directory is easily misunderstood and hard to configure perfectly?Anonymous
August 29, 2005
The comment has been removedAnonymous
August 30, 2005
Hi David,
The reason we do not want to use ISAPI filters involves procedural and responsibility/authority issues within our corporate management structure and does not involve any technical issues. Non development IT personnel within our company are responsible for installing and configuring ISAP filters on the several hundreds of websites within our company. This is because installing new versions of ISAPI filters on production web servers requires an IIS restart or frequently a reboot of the server. Also, our desired authentication mode, anaonymous aith Integrated windows authentication is very hard to configure with Microsoft's FrontPage. FrontPage, for some reason is much simpler to configure with Basic authentication which we would prefer not to use. However, this extra configuration often results in excessive configuration time and rebooting of production web servers. This leads to production downtime. Developers and other content-management personnel have authority and responsibility over ASP and ASP.NET content including configuration files and binaries. It is for these reasons that we prefer a pure .NET solution that does not involve ISAPI. BTW, we are trying to remove FrontPage as part of our publishing process but we are not there yet.Anonymous
August 30, 2005
Hi David,
It's me again. A good solution for our issues would not have to support any version of IIS prior to IIS 6.0. This is because we already have a plan in place to move all of our servers to an IIS 6.0 / Windows 2003 server environment and this is progressing well.Anonymous
September 05, 2005
The comment has been removedAnonymous
September 06, 2005
Thanx for the great answer David, I didn't think of that. Just one more question: How does ASP.NET tell IIS that it did not handle the request so it can be passed along to the next handler? Do I do this with a .NET IHttpModule? What do you reccommend?Anonymous
September 06, 2005
SharpDog - Actually, ASP.NET 2.0 does this automatically with its DefaultHttpHandler, so it should just work by default.
The wildcard application mapping in IIS6 tells it to route all requests to ASPNET_ISAPI.DLL. The DefaultHttpHandler in ASP.Net tells it to re-route all non-handled requests back to IIS6.
This functionality is ASP.NET 2.0 and IIS6 specific and is the closest you can get to IIS/ASP.Net integration prior to IIS7, where we make it integrated in a perfectly extensible and performant way.
//DavidAnonymous
September 12, 2005
The comment has been removedAnonymous
October 24, 2005
In your response to SharpDog on September 06, 2005 5:42 AM, you mentioned something that interests me. Specifically you suggest we consider using the “ASPNET_ISAPI.DLL as a wildcard application mapping so that it filters all requests and allow you to apply ASP.Net behaviors onto requests handled by IIS”
I am looking for more information about the request processing sequence when static content is requested while the ASPNET_ISAPI.DLL is configured as a wildcard application mapping. I don’t quite see how this would fit into the “HOWTO: IIS6 Request Processing Basics” document you created.
More specifically, for protecting ASP.Net content I have developed an HttpModule that works quite nicely. If I were to use the ‘ASPNET_ISAPI.DLL’ as a wildcard application mapping, would it then be possible for me to use the same or similar HttpModule to protect static files and ASP? Given that I am using ASP.NET 2.0 and IIS6, I would assume that I would just need to place an appropriate Web.config file in the same directory as the content to be protected?
Would there be any reason not to do this? I think I’m missing part of the puzzle.
--MarkAnonymous
October 24, 2005
The comment has been removedAnonymous
November 07, 2005
The comment has been removedAnonymous
November 07, 2005
I believe I have found the solution to my problem:
I ran across a blog over at: ‘www.leastprivilege.com’:
Under a blog topic of “Protecting non-ASP.NET resources with ASP.NET 2.0”
(http://www.leastprivilege.com/ProtectingNonASPNETResourcesWithASPNET20.aspx)
It says:
“If you need more control, you can derive from DefaultHttpHandler and customize its behaviour. If you override the OverrideExecuteUrlPath method, you can modify the request path that gets handed back to IIS. You can also add new HTTP header to the request by adding entries to the ExecuteUrlHeader collection. This enables you e.g. to protect a classic ASP application with ASP.NET forms authentication and you can pass the user and role information via headers from ASP.NET to ASP.
Warning: Be sure to validate and authenticate the headers you pass with ExecuteUrlHeader, e.g. by adding a MAC using a secret that is shared between ASP and ASP.NET (HMACSHA1 would be a possibility).”Anonymous
November 17, 2005
The comment has been removedAnonymous
November 17, 2005
The comment has been removedAnonymous
April 07, 2006
I'm trying to filter non-ASP.Net content in much the same way. But I would like to add a custom header before the request is sent back to IIS. This way the ColdFusion application that will eventually be executed could read the injected header that was created by the HttpHandler.
I’ve gone round and round trying to get a custom handler (that is derived from DefaultHttpHandler) to inject custom headers into a request before it is passed back to IIS. Could you please post a small example that adds headers using the ExecuteUrlHeader collection.
I’m not sure if I should be making a calls to base.ProcessRequest() and then adding the headers to the base.ExecuteUrlHeaders collection or implementing something on my own.
Even a tiny pseudo code example would do wonders for my understanding. Please help!
Thanks in advance!Anonymous
August 02, 2006
Hi David,
I'm writing a DLL that includes ISAPI Filter & ISAPI Extension export functions together.
My ISAPI Filter does not use RAW_DATA notifications. I'm using PREPROC_HEADERS to handle "GET" requests. And using HttpExtensionProc function to handle "POST" requests (HSE_REQ_EXEC_URL).
While reading your "SHORT ANSWER" I noticed this sentence:
"... ISAPI Filter (we changed this in IIS7, but that is not the point of discussion here...)"
My question is what you changed in IIS7? Because if you removed ISAPI Filter's i'm not gonna use it in my dll to make my dll compatible with IIS6 & IIS7.Anonymous
August 03, 2006
Murat - what you are doing is fine for IIS6 and IIS7 - ISAPI Extension and ISAPI Filter are staying.
What I meant to say (and I have updated it to clarify) was that IIS7 uses a completely new extensibility API for both native and managed code (hence "changed" from ISAPI, the original way to extend IIS).
However, CGI, ISAPI Filter, and ISAPI Extension each require a compatibility module to function on IIS7. They are now third class citizens.
You may find the new API to be easier to do things than ISAPI, so you may want to write a new native module for IIS7 and beyond, and leave the ISAPI version for IIS6.
//DavidAnonymous
August 06, 2006
David,
I got two questions more:
1- Which one is faster you think; Handling "GET" requests in an ISAPI filter or ISAPI extension?
Processing speed is so important as my dlls are intercepting all incoming IIS requests.
Currently I'm using an ISAPI Filter to handle "GET" requests and I got an extension dll that handles "POST" requests (Executes them using "HSE_REQ_EXEC_URL" with dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR). Dequeing a request not seems logical to me :(
2- What makes faster "HSE_REQ_EXEC_UNICODE_URL" than "HSE_REQ_EXEC_URL"?
I got the following information from MSDN site.
HSE_REQ_EXEC_URL: Only executes URLs formatted in the codepage of the IIS server. It does not support international applications, and is slower than the Unicode version.
HSE_REQ_EXEC_UNICODE_URL: Only executes URLs formatted in Unicode characters. It fully supports retrieving any Unicode resource on a file system like NTFS.
Additionally: You are hurting me by calling filters and extensions as third class citizens. :) I miss win 3.1...Anonymous
August 07, 2006
Murat - It all depends on what you are trying to do.
I have no idea why you have to use ISAPI Filter to handle GET and ISAPI Extension to handle POST. Can you please explain what you are trying to accomplish.
HSE_REQ_EXEC_UNICODE_URL is faster than HSE_REQ_EXEC_URL because there is no internal conversion to Unicode. This is of course implementation-specific to IIS6 and may not hold true in future IIS versions.
Hey, don't shoot the messenger. :-) I am only telling you the brutal truth, that CGI and ISAPI are really a third-class extensibility mechanism on IIS7 for compatibility reasons.
//DavidAnonymous
August 08, 2006
The comment has been removedAnonymous
August 08, 2006
The comment has been removedAnonymous
August 10, 2006
The comment has been removedAnonymous
August 10, 2006
Gert - Please tell me what you are trying to accomplish.
Your configuration is intentionally causing an infinite loop whenever you try to access .asp resources.
It is not possible for ASP.Net 2.0 code to filter the response data that is not generated by ASP.Net.
Only with IIS7 can you do what you want, in the manner you describe.
//DavidAnonymous
December 04, 2008
Hi David! I am trying to pass user Roles info from ASP.net to classic asp by using CustomHandler class as Stefan Schackow explained in his book. Her is my code public override string OverrideExecuteUrlPath() { HttpContext htpCon = HttpContext.Current; StringBuilder userRoles = new StringBuilder(); RolePrincipal rp = (RolePrincipal)htpCon.User; string rolesHeader = ""; if ((rp != null) && (rp.GetRoles().Length > 0)) { foreach (string role in rp.GetRoles()) { userRoles.Append(role + ","); rolesHeader = userRoles.ToString(0, userRoles.Length - 1); } } else { rolesHeader = string.Empty; } this.ExecuteUrlHeaders.Add("Roles", rolesHeader); return null; } My problem is that "this.ExecuteUrlHeaders.Add("Roles", rolesHeader)" line generates error. The ExecuteUrlHeader is always =null. he error is Object reference not set to an instance of an object. Please help me out here and let me know how can I make it run. Thaks I am using .net 3.5 and windows 2003 Tayyab trana_ca@yahoo.comAnonymous
December 07, 2008
Tayyab - I suggest contacting standard ASP.Net support for your question. Forums at www.asp.net would be a good start. //DavidAnonymous
September 23, 2009
Hi David, Thank you for all the helpful information. I was thinking the mapping described on this page may solve my issue. I have a module that works perfectly on other applications like OWA 2007, SharePoint, etc. However when I try to implement it with OWA 2003 IIS 6 there are problems. The machine has ASP.NET 2 installed and it's selectable from IIS. Is it possible to do this wildcard mapping with outlook web access 2003? When I add the wildcard mapping it seems to break the application. I noticed it changes the ASP.NET setting when I add that mapping, so I was thinking that might be causing the problem. Also, would I need to implement anything specific in the module for this scenario to hand the request back to IIS? Any guidance you can provide is appreciated. Thanks. PeteAnonymous
July 28, 2011
The comment has been removed