July 2004 - Posts

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):

  1. 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)
  2. IIS will handle the request first and perform authentication on it (including all stuff related to certificates when using SSL for example)
  3. 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).
  4. 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).
  5. 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" ?>
<configuration>
  <system.web>
    <authentication mode="Forms">
      <forms name=".ASPXAUTH" loginUrl="login.aspx" />
    </authentication>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
  <location path="admin">
    <system.web>
        <authorization>
          <deny users="?" />
        </authorization>
    </system.web>
  </location>
</configuration>

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:

  <location allowOverride="true">
    <system.web>
      <securityPolicy>

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 | Technorati | Blinklist | Furl | reddit | DotNetKicks

Developers who've been using the XML Serialization classes in the .NET Framework will probably recognize the "System.InvalidOperationException" exception that is thrown whenever is problem occurs during the deserialization. In fact, the used code for deserizalization is as easy as this:

XmlSerializer ser = new XmlSerializer(typeof(TargetClass));
return (TargetClass) ser.Deserialize(stream);

However, whenever a problem occurs, the same kind of Exception is thrown with the cool message: "There is an error in XML document (x,y)" with x and y replaced by the row and column in the XML source where the problem occurs. The first trick you should know is to look at the InnerException property of the exception to find out what the real problem was. In fact, I just did something like this:

  • Serialize the class to XML as follows:
    XmlSerializer ser = new XmlSerializer(typeof(TargetClass));
    ser.Serialize(stream,
    this);
    stream.Close();
  • Deserialize again (other method):
    XmlSerializer ser = new XmlSerializer(typeof(TargetClass));
    return (TargetClass) ser.Deserialize(stream);

However, this still resulted in an exception :-(. The inner exception was as nice as the cryptic wrapper "InvalidOperationException": StackOverflowException. It took me quite some time to find out where the problem was. XML serialization uses the properties of the class's instance to make these persistent in the XML target stream. To make sure everything is working correctly, I needed to expose some attributes as properties to make these persistent (in fact, I only had some indirect methods that were setting the attributes, so these were not made persistent since there was no equivalent property for the attributes). Assume the attribute was:

private SomeOtherClass attribute;

and the corresponding property was:

public SomeOtherClass Attribute
{
   get
   {
      return attribute;
   }
   set
   {
      attribute =
value;
   }
}

Guess where the error was? Yeah, inside the setter of the method I made a mistake:

      Attribute = value;

Classic story, property calls its own to set the value, over and over again... However, not easy to find if the exception is wrapped up inside some other exception which is thrown during the deserialization process. In fact, the setter was only there to make the XML serialization possible, so it was not tested upfront (shame on you, Bart :-)).

Now I'm happy. Not because the exceptions are gone, but because I end up with another one. Okay, it's the "InvalidOperationException" again but this time the InnerException is "The data at the root level is invalid". Ready for the next debugging burst...

The moral of the story: be happy with exceptions always :-)

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Okay, developer guys. Just go ahead and take a jumpstart with the free Express tools that were released a few days ago on TechEd Europe 2004. These are lightweight versions of the development tools (Whidbey/Yukon timeframe) especially built for hobbyists and informatics enthousiast to get to know the products better. Products include: the Express editions of Visual VB, C#, C++, J#; the web developer tool (a web project is not longer a child of a language products, it's just a web project that consist of multiple files written in various languages if you want to do so, more on this in later articles) and SQL Server 2005 Express Edition (a little brother of SQL Server 2005 with support for databases up to 4 GB in size; think of it as a small SQL Server and a big MSDE that includes the management tools). More in on http://lab.msdn.microsoft.com/express/.Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Of course there was the ATE for Virtual Server again. Beginning to like the DSI initiative (Dynamic Systems Initiative) more and more. That was one of the reasons why I attended a talk about ADS (automated deployment services) in the afternoon (the last session before the country party actually). We're getting quite a lot of questions about VSMT (Virtual Server Migration Toolkit) and the way it's (or better: it will) be implemented is through the use of ADS. To summarize what it is: an aid to help you migrate a physical machine to a virtual machine without having to reinstall the OS or altering the configuration of it.

The forenoon was all about ASP.NET (actually I was a little late at the ATE pavillion but solved that by staying for another hour after my official shift :-)). In fact I knew most of the ASP.NET v2.0 things already (to be honest: I knew everything that was presented already) but it's always nice to see the people's reactions during the creation of a complete site with as little as 4 lines of code (in fact, trivial lines of code). Furthermore, since we're planning to do some ASP.NET related stuff in Belgium the next months it's always interesting to learn some presentation tricks for a good overview session by the mister himself: Scott Guthrie.

In the afternoon, I had a 1-on-1 meeting about ASP.NET v2.0 and related stuff (future plans for ASP.NET and IIS) with Scott Guthrie and I attended one more ASP.NET session by Rob Howard about tips and tricks to make your web app better perform (based on the experiences of the ASP.NET forums development).

Last but not least: the country party of Belux in the "Maritime Museum" after a boat trip from the RAI to the center of A'dam. The first few hours were all about the UEFA (I hope that's correct, the only thing I know it was about soccer since I'm not a sports fan - except for golf and snooker - at all) match of The Netherlands and Portugal (1-2). Had some interesting talks and arrived at the hotel afterwards at about 2 AM (not that late, isn't it?).

Off to a few sessions now (I have ATE in the afternoon starting from 2 PM on) and of course ... there's another party tonight: the official TechEd party. See ya there.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

More Posts