I'm receiving quite a lot questions about security in ASP.NET (and I'm seeing the same questions all over again through the ASP.NET Forums). Although some of these questions are fairly easy to answer, I thought it's worth the work to make some little FAQ here on my blog with some tips and tricks to solve these problems. You can expect to see more of this stuff later on in next parts of this "ASP.NET Security Overview" :-)
Part 1 - Authentication and authorization
ASP.NET support authentication and authorization on your web app as you all should know. I hope the difference between authentication (prove who you are in some way) and authorization (who can do what to what?) is clear. In fact, there is support for 4 kinds of authentication: None (or anymous), Windows, Forms (authentication on a web page) and PassPort.
Question 1: How does ASP.NET security relate to IIS security?
This is one of the main "problems" people are faced with when they are building secure web applications today. The question however is pretty simple. IIS security comes before ASP.NET security. So, if IIS does not allow the user to get in to the system, ASP.NET won't be called in any way and thus the security of ASP.NET does never get a chance to do its job since the request has been denied already (or authentication on the level of IIS is required first). To solve this kind of problems, always think of the workflow an HTTP request goes through when it comes in to IIS and is meant to be handled by ASP.NET (e.g. since it has an .aspx extension):
- HTTP requests comes in through some TCP port and reaches IIS (on IIS 6 on Windows Server 2003, it will pass through http.sys first)
- IIS will handle the request first and perform authentication on it (including all stuff related to certificates when using SSL for example)
- Then authorization is performed by IIS, that is: what are the web permissions (set on the level of the web site or the vdir), NTFS ACLS (everything which has to do with the security on the file system level), IP address restrictions (allow/deny requests from ..., set through the config in IIS). If the request passes these tests, it can go to the ISAPI that will handle it, in our case ASP.NET, with the appropriate credentials (IUSR account for anonymus access, a Windows account principal when using Windows auth, etc).
- Now ASP.NET comes into play (in IIS 6, the application pool will pass in as well) and your web application will do its work based on the used settings in web.config for your app (that is, authentication and authorization in there).
- ASP.NET runs in its own context (in the case of pre-IIS 6, it's the process called aspnet_wp.exe with identity ASPNET; in IIS 6, the application pool's identity is used). You should keep this in mind when you're calling the filesystem from within the application since the used credentials to gain access to the files will be the ASP.NET process's credentials. The same holds when you're using Windows authentication on a SQL Server for example. If you want ASP.NET to run in the context of the logged on user to the application (when using Windows authentication), you can use impersonation.
Question 2: How to enable impersonation?
Impersonation goes hand-in-hand with Windows authentication, so you'll find the next configuration snippet in the web.config file when using impersonation:
<authentication mode="Windows" />
<identity impersonate="true" />
If you don't wont to impersonate as the logged on user, but as a fixed user, you can put the following configuration in the web.config file:
<identity impersonate="true" userName="..." password="..." />
where you, of course, replace the userName and password attributes according the ones you want to use. One important remark should be kept in mind (although it should not be needed anymore, since it only applies to ASP.NET v1.0): the ASPNET account needs he "Act as part of the OS" privilege in the policy of the system to do impersonation with a fixed identity. This is not the fact in ASP.NET v1.1 (the IIS process handles the impersonation logon). However, it's no a good idea to put the credentials in clear text in the config file, but I'll cover this problem in a further FAQ question.
Question 3: How to deny users to download .doc, .pdf, .zip, etc files directly from my application without authenticating themselves first?
The basic problem with this is that ASP.NET never receives the requests for that kind of extensions by default. To understand this, you should be familiar with the concept of ISAPI's on IIS. Let me explain this somewhat better: IIS is not a closed rigid black box. Instead, it has an API that allows people to write extensions to it. The basic principle is rather easy: requests have an extension that characterizes them. When such a requests comes in, IIS will take a look at the extension of the request. If that extension occurs in the configuration of IIS as a mapping to some ISAPI extension, that extension will be called (and of the request stuff will be passed to that extension). The extension can then handle the request further. ASP.NET is such an extension (aspnet_isapi.dll) that is registered for a set of extensions (such as .aspx, .ascx, .asmx, .vb, .cs, .config, etc). When you take a look at the machine.config file, you'll find out that there is some section called <httpHandlers> that contains the mappings of these extensions in ASP.NET itself (so that's beyond the level of IIS already). An example is this:
<add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler" />
which tells ASP.NET that the extension .cs can't be downloaded (it will issue a 403 error). You can do the same inside the web.config file (add a httpHandlers section with a configuration line as the one above) to deny requests to a certain file extension in ASP.NET. However, before you can do so, you should map that extension to the ASP.NET ISAPI in IIS first, otherwise it won't never be effective. The way to do this depends somewhat on the version of IIS being used. However, you'll find the information you need in the help of the used OS.
To make requests to certain file types requiring authentication, you should map the file extension to the ASP.NET runtime and there you go. Since the file is now handled by ASP.NET it will be intercepted (according to the authorization section in your web.config file) if a request to it is not allowed in the current situation (e.g. when you're not logged in or when you're not in a certain role). The way this works is by the use of an HttpModule (you should not do anything to get it to work, it's by default in the ASP.NET configuration in machine.config) that is called during each and every request (that's the characteristic of an HttpModule) to see whether the request is allowed or not. You can see this in the <httpModules> section in machine.config:
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
The ones I've listed in here are the ones that have to do with authentication and authorization.
Question 4: Certains folders and/or files should only be accessible after authentication, while others can be available for everyone. How to do this?
The answer is in the <location> element that can be used in the web.config file. In fact, the use of it for authentication/authorization settings is only a limited set of its capabilities since all arbitrary configuration information specific for the specified location can be stored in there. An example to lock down an administrator section of a web application looks as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<forms name=".ASPXAUTH" loginUrl="login.aspx" />
<allow users="*" />
<deny users="?" />
Pretty easy, isn't it? In fact, the specified path can be a folder and/or a file (relative to the root of the application). Another point to take care of is this: web.config files can override each other's settings. In fact, the web.config file on the lowest level of the folder hierarchy will override settings set by web.config files on a higher level. So, the "nearest configuration" is applied to the files/folders that fall under the scope of the web.config file. If you don't want specific settings to be overriden at a lower level, you can use the allowOverride attribute of the <location> tag and set it to false. That way, these settings can't be overridden anymore by web.config files deeper in the hierarchy. In fact, the machine.config file is using this as well:
By using this setting in machine.config one can set that the securityPolicy can be overriden on the whole machine (on every application on it). As you can see, no path was specified which means (in machine.config) that the whole machine takes this setting. When you change the true value to false, the policy can't be changed by any application anymore (can be a security recommendation for hosting environments for example).
Question 5: How to store forms authentication passwords in a database in a secure way?
This is one of the most seen question on the forums around the net. Forms authentication is a great way to do authentication on websites (without having annoying pop-ups to enter a username and password) and ASP.NET has great support for it. In ASP.NET v2.0 this feature will be even more interesting since the system will take care of authentication using a series of new controls (login control, registration control, login status control, etc). Back to today's reality however. There are in fact two ways to encrypt passwords before you store these in a database: using reversible encryption or using non-reversible encryption. The latter is the best solution and is natively supported by the FormsAuthentication class in System.Web.Security. However, if users forget their password, there is no way to send their original password by using e-mail. Instead, you should have some secret question/secret answer system in there or you can use another mechanism that sends some random key to their e-mail that can be used to unblock their account and to fill out a new password.
Now, how to do it? On the registration page, you take the password of the user (twice for confirmation, make sure it's a password field as well to mask the password, and eventually use a CompareValidator to check both passwords for equality). Before storing the password in the database, you should now encrypt it using the FormsAuthentication.HashPasswordForStoringInConfigFile method (I guess that's one of the longest public methods in the whole framework :-)). It takes two parameters, the first is the password string, the second is the used hash algorithm (MD5 or SHA1 as a string).
In the login page, take the filled in password of the user and apply hashing to it again (using the same algorithm of course). Then compare this retrieved value with the one in the database. If both hashes are equal, the password was correct. Otherwise, it wasn't and authentication should fail of course. Remark: this hasing method is in fact a wrapper of the encryption API in the .NET Framework (System.Security.Encryption) so that it takes strings and returns strings in an easy way. The real stuff behind the scenes is using the MD5 and SHA1 classes which work with byte arrays to do encryption (so you'd need the use of System.Text.Encoding as well to get it back to some string). I strongly recommend to use the System.Web.Security namespace to do the job since this is the easiest one (and it's secure).Del.icio.us
| Digg It