
Asp Net 2.0 Security Membership And Role Management
.pdf
Chapter 8
protected void Page_Init(object sender, EventArgs e)
{
this.ViewStateUserKey = User.Identity.Name;
}
With this code, even if a malicious user attempts to submit hijacked viewstate information, the postback will fail because the viewstate hash is now derived in part from the user’s name.
You have to set the ViewStateUserKey property early on in the page lifecycle during the Init event. Because the property value affects the deserialization and validation of viewstate, ASP.NET has to have the correct ViewStateUserKey value before it attempts to process the viewstate. Setting ViewStateUserKey during a page’s Load event is far too late because by that point ASP.NET has already deserialized viewstate.
ASP.NET 2.0 introduced one new option for determining when viewstate encryption occurs: a new property on the Page class called ViewStateEncryptionMode. The possible values for this property are Auto, Never and Always, with the default being Auto. You can set this value globally in configuration using the viewStateEncryptionMode attribute of the <pages /> configuration section. You can also customize the value on a per page basis using the ViewStateEncryptionMode attribute of the @Page directive. Although you can set the property at runtime, either the configuration setting or the page directive are the normal approaches for setting this value. If you attempt to programmatically set
ViewStateEncryptionMode, you will need to do so in an override of the FrameworkInitialize method on the page class. This is a new “ultra-early” initialization method where you can set various page properties that really can’t be set during the normal page initialization phase.
During viewstate serialization, the Page class and the ObjectStateFormatter class look at the ViewStateEncryptionMode property before looking at the setting for EnableViewStateMac. Clearly, if the property setting is Never, nothing else happens and the ObjectStateFormatter follows the ASP.NET 1.1 behavior for hashing and encrypting viewstate. However if ViewStateEncryptionMode is set to Always, regardless of the page’s current setting for EnableViewStateMac, ASP.NET will always encrypt viewstate. Furthermore, this encryption will use the encryption algorithm determined by the decryption attribute on <machineKey />. So by default, this means with a setting of Always, your page’s viewstate will be encrypted using AES. Two things to keep in mind if you set
ViewStateEncryptionMode to Always:
The encryption options in the validation attribute are ignored. Forcing viewstate encryption means that the selection of the encryption algorithm follows the rules for forms authentication.
The other validation options in the validation attribute are also ignored. When ViewStateEncryptionMode forces viewstate encryption, only encryption occurs. No hashing of the viewstate data stream occurs. However, if you set a value for ViewStateUserKey, it will be added to the encrypted data stream, so you still gain the extra viewstate protection of this property.
The last (and the default) option for ViewStateEncryptionMode is Auto. The Auto setting is intended for use by controls in conjunction with the new Page method RegisterRequiresViewStateEncryption. Because the default page setting is Auto, various controls in the Framework, or third-party controls, can proactively turn on viewstate encryption if the controls “know” that they deal with sensitive data. The idea behind the Auto setting is that individual control developers know the guts of their code much better than the developers using them do. Rather than forcing developers to slog through lengthy API documents to determine whether sensitive data is being processed by a control, a control developer can just make that determination up front.
312

Security for Pages and Compilation
If a control calls Page.RegisterRequiresViewStateEncryption and the current
ViewStateEncryptionMode is Auto, regardless of the EnableViewStateMac setting, the page’s viewstate will end up being encrypted. Because the default setting of EnableViewStateMac is true, but the validation attribute in <machineKey /> defaults to SHA1, under normal conditions all of your page’s viewstate is for all practical purposes being transmitted in the clear. Even though the hidden __VIEWSTATE is base64 encoded, with the default behavior there is nothing preventing a user from un-encoding the field and looking at the raw data. The ViewStateEncryptionMode behavior allows a control to increase the security of the page’s viewstate by forcing this data to be encrypted, even when the page developer may not realize that sensitive information is being stored in viewstate.
Within ASP.NET, the following controls (all of them are data controls) may call
RegisterRequiresViewStateEncryption:
FormView — If there are any key values in the DataKeyNames property, FormView forces viewstate encryption.
DetailsView — If there are any key values in the DataKeyNames property, the DetailsView forces viewstate encryption.
GridView — If there are any key values in the DataKeyNames property, and the control is not auto-generating the columns used in the GridView control, then GridView forces viewstate encryption.
DataList — If there is a key value stored in the DataKeyField property then the DataList forces viewstate encryption.
As you can see these are all new data controls in ASP.NET 2.0. You should keep this new behavior in mind if you port your old ASP.NET 1.1 data control logic over to use the new ASP.NET 2.0 data controls.
If you choose to store the primary key values in these controls (and for some control scenarios you need to do this), you will end up triggering viewstate encryption. This isn’t a “bad” thing, because chances are that you don’t want the outside world looking at your database primary keys through reverse engineering client-side viewstate. However if your application works perfectly in development, but fails when you push it out to your web farm, the ViewStateEncryptionMode behavior might be causing the problem. Because viewstate encryption uses the encryption key material from <machineKey />, and <machineKey /> by default sets the decryption key to AutoGenerate, IsolateApps, your data pages can fail in a multiserver web farm. As with forms authentication there is a simple solution: if you use any of these four controls and you run in a web farm, explicitly set the decryptionKey attribute in <machineKey /> and synchronize the value across all of your web servers.
One thing to keep in mind with ViewStateEncryptionMode is that you are not always guaranteed that encryption will occur. If the page has explicitly turned off ViewStateEncryptionMode by setting it to Never, regardless of whether a control requests view state encryption, the page is not going to force encryption. In this case, only the protections specified in the validation attribute of <machineKey /> will apply. The interaction between ViewStateEncryptionMode and a control results in a more secure page only if the mode is set to Auto and if no other steps have been taken to turn off viewstate encryption for the page.
313

Chapter 8
Page Compilation
The new dynamic page compilation model in ASP.NET 2.0 does away with the monolithic code-behind assembly from ASP.NET 1.1. Instead, developers can just author their page markup and code-behind pages, and then deploy all of the content to a web server. Although this model of XCOPY everything works well inside of a corporate firewall, for Internet-facing applications administrators understandably may not want the .vb or .cs code-behind files existing on their production servers. To address this issue, ASP.NET 2.0 introduces the concept of precompilation. A precompiled website is one where ASP.NET has already converted the page code and markup into multiple assemblies. The output from precompilation are just a series of .aspx/.ascx files along with compiled code in multiple assemblies sitting in the /bin directory.
With a precompiled site, the page and user control files that are left in an application’s folder structure can optionally include the original markup because there are two modes of precompilation: updatable and non-updatable. If you use updatable precompilation the markup is preserved in the .aspx and .asx files. Non-updatedable precompilation still generates .aspx files, but these files are just empty stubs. In either case, you can use precompiled sites to ensure that your assemblies are deployed to a production server without the need to push any page code.
You can invoke precompilation in two ways. The easiest is to just select Publish Website from the Build menu option in Visual Studio 2005. (Note: this option does not exist in the Express editions of Visual Studio 2005.) You can also invoke precompilation using the aspnet_compiler.exe program that is located in the framework installation directory. The command-line tool is useful if you have an automated build process that you are currently using for building websites. When you move to ASP.NET 2.0, you can update your build process to invoke the aspnet_compiler tool instead. A command-line invocation looks something like this:
aspnet_compiler -m /LM/W3SVC/1/Chapter8/PageSecurity d:\inetpub\wwwroot\somedir
You can also reference your application code using a physical path or a virtual path. The preceding example uses an IIS metabase path to reference the specific application that should be compiled.
Some developers in ASP.NET 1.1 took advantage of the code-behind assembly by signing it. Then on their web servers, they had Framework CAS policies that only allowed signed assemblies with a specific public key to run, or that restricted permissions based on specific public keys. If you want to accomplish the same thing in ASP.NET 2.0, you must use precompilation. Both the Visual Studio 2005 UI and the command-line compiler give you the option to sign your precompiled assemblies. You will need to generate a .snk file with the key material ahead of time. After you have generated the public/private keypair you can then use either Visual Studio 2005 or the command-line compiler to generate and sign the precompiled assemblies. In Figure 8-1, you can see an example of precompiling a website and signing the precompiled assemblies.
314

Security for Pages and Compilation
Figure 8-1
Notice that updatable precompilation wasn’t selected. This ensures that all of the code in the site is compiled ahead of time and that no dynamic generation of page classes will occur at runtime. This also means that all of your application code, including any inline code on an .aspx page or .ascx control will be stripped out and compiled into precompiled assemblies. Also note that the Mark assemblies with APTCA option is checked. This is necessary if you want to run a signed precompiled site in anything less than Full trust.
315

Chapter 8
In Figure 8-2, you can see the result of signing precompiled output in ildasm.
Figure 8-2
The precompiled assembly called App_Web_ho0y5wqc.dll now has a public key embedded in its manifest.
With the signed assembly, you can use the .NET Framework Configuration MMC (Look for mscorcfg.msc in the directory where you installed the Framework SDK. The tool is no longer installed as part of the Framework itself) to set up a code group with a public key based membership condition. If precompilation outputs multiple assemblies (which will normally be the case), you can just choose one of the assemblies for purposes of setting up the public key based membership condition. Figure 8-3 shows the step in the wizard that walks you through creating a new code group with a strong-name membership condition.
In this wizard step, the Strong Name condition has been chosen. In the File dialog box, the precompiled assembly has been selected so that the wizard will extract the public key token from it. Once the token is extracted, the wizard enables you to choose a permission set to associate with assemblies that match the membership condition. Although ASP.NET trust policy files are really the de rigueur approach for granting permissions to web applications, you may be in an environment where permissions are also locked down using the Framework’s CAS policies. After you set up a new code group, you can use the .NET Framework Configuration MMC to associate a custom permission set for your precompiled ASP.NET sites.
316

Security for Pages and Compilation
Figure 8-3
Although it is not new to ASP.NET 2.0, you can change the location of the temporary files used by ASP.NET at runtime. Normally, any type of temporary per-application file storage for ASP.NET is placed somewhere in the following directory:
%windir%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\
One reason you might want to change the location is that you install the framework onto your system drive, but you want the auto-generated compiler output, spooled data from large requests, and so on to be located on a separate drive. If you host a large number of applications, it is possible to have a very large file structure within the Temporary ASP.NET Files location, in which case the system drive may not be the right place for them.
From a security perspective, the fact that many different applications are sharing the same general directory structure can also be troublesome. Even though there is no way for code in a partially trusted web application to reach out into this directory structure, many ASP.NET sites still run in Full trust. A malicious developer could take advantage of a fully trusted application and write code to open and read the temporary files in this directory structure from other applications. As a side note, this is another reason why running in Medium trust for untrusted hosting environments is so important; this attack vector simply isn’t available in Medium trust.
317

Chapter 8
If you want you can change the location used by ASP.NET for storing its temporary files with the tempDirectory attribute of the <compilation /> configuration section. For example, the following configuration section remaps the temporary file location to a location on the D drive.
<compilation tempDirectory=”D:\Chapter 8\NewTempDirectory” />
Of course, just changing the location of the temporary directory is not sufficient. You also need to ensure that the process account, or the application impersonation account if you are using application impersonation, has the following directory rights:
Read/Read & Execute/List Folder Contents
Write
Modify
Special Permission: Delete Subfolders and Files
Special Permission: Change Permissions
These are the same set of rights granted to accounts on the Temporary ASP.NET Files directory if you use the aspnet_regiis -ga option in ASP.NET 2.0 to configure nondefault process accounts. After you configure the NTFS ACLs appropriately, you will see that your web application uses the new tempDirectory location for all temporary ASP.NET files.
Fraudulent Postbacks
ASP.NET relies heavily upon postbacks and on the client-side postback logic that the runtime emits. With ASP.NET 1.1, there is a potential security issue with postbacks because the client-side JavaScript that triggers postbacks is easy to modify. This security issue is referred to as the fraudulent postback problem. To illustrate the problem, you can construct a simple page with some ASP.NET controls that use the client-side postback logic.
<form id=”form1” runat=”server”> <div>
<asp:LinkButton
ID=”btnSensitive” runat=”server” Visible=false
OnClick=”btnSensitive_Click”>Click Me!</asp:LinkButton> <br />
<a href=”javascript:fraudulentPostback()”>Trigger fraudulent postback</a> <br />
<asp:LinkButton ID=”LinkButton1” runat=”server”> Ignore Me!</asp:LinkButton></div>
<script type=”text/javascript”> function fraudulentPostback()
{
var theForm = document.forms[‘form1’]; theForm.__EVENTTARGET.value = ‘btnSensitive’;
318

Security for Pages and Compilation
theForm.__EVENTARGUMENT.value = ‘’; theForm.submit();
}
</script>
</form>
This ASP.NET page has two LinkButton controls: I chose that control type because LinkButton(s) emit the __doPostBack function and the supporting form variables used by ASP.NET for submitting postbacks. Note that the same issue can also be triggered with less complex server-side controls such as the Button control that don’t rely on the __doPostBack method. In the sample page, the first LinkButton has its Visible property set to false. Many developers use control visibility or the enabled/disabled state of a control as a kind of surrogate client-side security mechanism. For instance, you might intentionally hide a set of update controls on a page if you know the current user has only view rights to a piece of data.
The reason for the second LinkButton on the page is simply to force the rendering of the hidden __EVENTTARGET and __EVENTARGUMENT fields for this example. Most moderately complex ASP.NET pages will have multiple controls on them that can trigger postbacks, so even if one set of controls is disabled or hidden, the other controls will still trigger the rendering of these hidden fields. The sample page has an <a> tag that points at a JavaScript function called fraudulentPostback. The code in the function contains a copy of the JavaScript from the __doPostBack function — with the one modification being that fraudulentPostback hardcodes the event target as the btnSensitive control. In other words, the fraudulentPostback function is faking the postback process that would occur if btnSensitive were visible on the page, and the browser user clicked it.
The server code for this page is very basic: the click event for the hidden link button simply writes some text:
protected void btnSensitive_Click(object sender, EventArgs e)
{
Response.Write(“Sensitive operation has been carried out.”);
}
The problem in a real application, of course, occurs when the click event for a hidden or disabled control actually carries out a sensitive operation based solely on the assumption that the postback data can be trusted.
When you run the page in the browser, the HTML for the form includes only the following control tags:
<form name=”form1” method=”post” action=”FraudulentPostback.aspx” id=”form1”>
...
<input type=”hidden” name=”__EVENTTARGET” id=”__EVENTTARGET” value=”” /> <input type=”hidden” name=”__EVENTARGUMENT” id=”__EVENTARGUMENT” value=”” />
...
<a href=”javascript:fraudulentPostback()”>Trigger fraudulent postback</a> <br />
<a id=”LinkButton1” href=”javascript:__doPostBack(‘LinkButton1’,’’)”> Ignore Me!</a>
...
319

Chapter 8
Notice that the rendered HTML does not have an <a> tag for the btnSensitive LinkButton control. At this point though, you can still click on the LinkButton1 link button. ASP.NET is fooled into thinking that the browser user actually clicked the nonexistent btnSensitive link button, and as a result the code in btnSensitive_Click runs. In a nutshell, this entire process is the crux of the fraudulent postback problem. As long as someone can load a page in a browser and have it run JavaScript, it is possible to run JavaScript code that sends postback data to ASP.NET for controls and actions that don’t actually exist on the rendered HTML page.
The first line of defense against this problem is simply to use defense-in-depth coding techniques in your web application. A security-conscious developer would not trust the postback data in a server-side event. Instead of assuming that just because a server-side event has been fired that the business logic within it is safe to run, you would perform server-side authorization checks. For example, you could perform a role-based authorization check in the click event that confirms the current user is in the appropriate role in before it carries out the requested sensitive work. Alternatively, you could perform the same type of security check farther down in your middle tier.
Unfortunately, not all developers are diligent about building this level of security into their applications. If an application relies solely on the presentation tier doing the right thing, then it is rather easy to forge postbacks as you just saw. ASP.NET 2.0 introduces a new layer of protection called event validation that specifically addresses the problem of fraudulent postbacks.
By default, event validation is turned on in ASP.NET 2.0. So, if you were to take the code shown earlier and run it on ASP.NET 2.0, instead of the btnSensitive_Click event running, you get an exception and stack trace like the following:
[ArgumentException: Invalid postback or callback argument. ...] System.Web.UI.ClientScriptManager.ValidateEvent(String uniqueId, String argument) System.Web.UI.Control.ValidateEvent(String uniqueID, String eventArgument) System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String eventArgument)
...
Here, the LinkButton control makes use of the new event validation feature in ASP.NET 2.0. When the postback event is passed to the LinkButton, it in turn uses the ClientScriptManager object to validate that the current event is actually valid. Because the LinkButton control is actually not visible
on the page, clearly the postback event could not have been triggered by it, and as a result the exception occurs.
Event validation can be controlled globally in an application with the enableEventValidation attribute in the <pages /> configuration section. You can also turn validation on or off on a per-page basis with the EnableEventValidation attribute on the @Page directive. There is a property on the Page class of the same name that you can set as well, although you can only set the EnableEventValidation property during FrameworkInitialize. By default, event validation is turned on for all pages in ASP.NET 2.0.
When event validation is enabled, and a control that makes use of event validation is on the page, the following general steps occur when the page runs:
1.When the control is creating postback event references for a page, it also calls the
RegisterForEventValidation method on the ClientScriptManager object associated with the page. Internally the ClientScriptManager creates and stores a hash value of the data that is passed to the RegisterForEventValidation method. A control can choose to hash just a string
320

Security for Pages and Compilation
that uniquely identifies the control, a combination of both the control’s identifier and the event arguments, or a hash can be generated from an instance of PostBackOptions. For example, the Button control generates a validation hash using its PostBackOptions, while the GridView hashes its UniqueID and the event arguments for the postback reference being created.
2.The ClientScriptManager then takes all of the hash values that it created, and it serializes them into a hidden input field called __EVENTVALIDATION. The hidden input field is protected in the same way that the hidden __VIEWSTATE field is protected. By default the serialized representation of the event validation hash codes is itself hashed using the <machineKey /> information, and this value is included in the __EVENTVALIDATION field. If encryption has been enabled (or was forced on due to the new ViewStateEncryptionMode settings), the information will be encrypted.
3.When a postback subsequently occurs, the postback is raised to a specific control on the page. For example, if a control implements IPostBackEventHandler, then if an event reference for that control triggered the event, ASP.NET will call the control’s RaisePostBackEvent implementation. At that point, it is the control’s responsibility to call ClientScriptManager
.ValidateEvent, passing the same set of parameters to ValidateEvent that were originally passed in to the RegisterEventForValidation method. If you are authoring a control
that registers for event validation with PostBackOptions, you will need to pass the
PostBackOptions.TargetControl.UniqueID and PostBackOptions.Argument properties to ValidateEvent because there is no ValidateEvent overload that accepts an instance of
PostBackOptions.
4.The ClientScriptManager delay loads the data in the __EVENTVALIDATION field. If no controls on the page ever call ValidateEvent, then the ClientScriptManager does not need to deserialize the event validation information, thus saving processing overhead. Only when ValiateEvent is called for the first time during a postback will the ClientScriptManager derserialize the event validation information.
5.Inside the ValidateEvent method, the ClientScriptManager looks at the string identifier and optional arguments that were passed to it. It hashes these values and then checks in the deserialized event validation information to see if the same hash values exist. If a match is found, then the postback event and its associated arguments are valid (that is, the postback event and its arguments were originally rendered on the page). If the hash of the information that the control passed to ValidateEvent cannot be found, this is an indication that a forged postback has occurred. In this case, the ClientScriptManager throws the exception that you saw earlier.
On one hand, the net result of all of this work is that if a control registers for event validation, and the set of event information that was registered arrives at the server during a subsequent postback, then the postback will be considered valid. On the other hand, if event data posted back to ASP.NET comes from an event reference that was never rendered, or a control that was never rendered, when the ClientScriptManager attempts to find a previous registration for the event or control it fails and throws an exception.
One thing to note about event validation is that it is not an ironclad guarantee that a postback is valid. Event validation is only as strong as its weakest link — specifically the hidden __EVENTVALIDATION field. Just as viewstate from one user can potentially be hijacked and submitted by a second user, the same attack vector exists for the event validation field. However, because the event validation field is protected in the same way as viewstate, you can set a ViewStateUserKey that will make the event validation field unique to each user.
321