Securing Application Pages in Windows SharePoint Services 3.0

Summary: Application pages provide quick access to the SharePoint object model—but that power includes risks. Learn how to improve the security of your application pages. (6 printed pages)

Rafael Perez, Catapult Systems

June 2009

Applies to: Windows SharePoint Services 3.0

Contents

  • Application Pages: The What and Why

  • Validating Page Requests

  • Validating User Permissions

  • Conclusion

  • Additional Resources

Application Pages: The What and Why

If you do a lot of development for Windows SharePoint Services, sooner or later you will probably need to develop application pages. Also known as "layout pages," application pages provide quick access to the SharePoint object model, are relatively easy to work with, and seamlessly integrate with the context of the site from which they are accessed.

Application pages are accessible from each site via the _layouts virtual directory. They are deployed directly to the file system (once per Web server), and although you cannot customize them for each site, they do inherit the site themes.

Application pages are a powerful tool that can quickly execute complicated and often risky logic. Because of the ease of development and seamless integration of application pages, developers do not always give enough thought to security. Although Windows SharePoint Services handles much of the security, developers often write code that circumvents this integrated security logic. Doing so makes application pages vulnerable and exposes SharePoint sites to great risk. In this article, I present several techniques that you can use to improve the security of your application pages.

Application pages can be accessed from any SharePoint site and by any user. Not uncommonly, code blocks execute via the RunWithElevatedPrivileges method of the SPSecurity object. This method allows code to execute as though the user of the current context had full control, regardless of the actual permissions the user may have been given. Such pages can compromise the stability and security of your server farm, depending on how well your code validates page requests. The SharePoint object model exposes many methods to help developers easily validate page requests and user permissions.

Validating Page Requests

You cannot always rely on checking user permissions or roles to determine whether certain logic should execute. Consider a scenario in which a set of application pages moves or renames files in specific document libraries or of a specific content type. The interaction and user experience in these pages is carefully controlled, indicating to users the files they can and cannot move or rename. However, the methods responsible for the execution of the logic rarely check that the steps were followed; these are usually tied to the OnClick event of a button, or processed in a different page altogether. The methods within the pages might require only a properly formatted post with the identifiers of the source list and the file to move. The natural assumption is that the post originates from a page within your own site, where you control the logic of which list or file identifier gets posted; but given a little time, one could build a separate page (maybe hosted on a different site) that bypasses all those steps and posts a malicious request.

One way to avoid such an attack is implementation of the FormDigest control, which generates a security validation that is passed when the form is submitted; as the request is processed, the validity of the request is verified via a call to SPWeb or SPUtilityValidateFormDigest method. Although Windows SharePoint Services automatically calls ValidateFormDigest with most write operations, you should explicitly call the method before executing code via the RunWithElevatedPrivileges method.

For example, an ASP.NET form page could implement the FormDigest control as follows:

<form id="form1" runat="server" action="customaction.aspx"> 
  <SharePoint:FormDigest runat="server"/> 
  <asp:Button id="Button1" Text="Button" /> 
</form> 

The page processing the request validates the request via the SPUtility.ValidateFormDigest method before calling RunWithElevatedPrivileges:

protected override void OnLoad(EventArgs e) 
{
    SPUtility.ValidateFormDigest(); 
    SPSecurity.RunWithElevatedPrivileges(delegate() 
    { 
        // Execute application page logic. 
    }); 
}

Pay close attention to the ASP.NET page life cycle. If you call RunWithElevatedPrivileges during the pre-render stage, you are too late—most of the logic has probably executed.

Validating User Permissions

You cannot rely solely on base permissions, role definitions, or group membership to determine whether code should execute, but they will be part of your security requirements in most cases. Let's examine each of these permission types. I begin with base permissions because they represent the lowest level of permissions we can reference by using the SharePoint API.

Base Permissions

Base permissions are the built-in permissions available in Windows SharePoint Services. These are a very granular set of permissions covering everything from "View Pages" and "View List Items" to "Enumerate Permissions" and "Use Remote APIs". (Refer to the SPBasePermissions enumeration of the Microsoft.SharePoint assembly for a full list.) Not many application-page scenarios require you to validate permissions at this level, because doing so bypasses the role-based and group-based approaches. However, roles and groups can be modified, renamed, and even deleted through the front end, so you cannot always depend on them for your application pages. In either case, you can determine whether a user has a specific base-level permission by using the following code:

using (SPWeb site = this.Web) 
{ 
    // Validate the page request to avoid 
    // any malicious posts. 
    if (Request.HttpMethod == "POST") 
        SPUtility.ValidateFormDigest(); 

    // Determine whether the user has full control 
    // over the site. If not, redirect the user 
    // to the access-denied page. 
    if (site.DoesUserHavePermissions(SPBasePermissions.ManageWeb)) 
    { 
        // Execute application page logic. 
    } 
    else 
    { 
        Response.Redirect("/_layouts/accessdenied.aspx"); 
    } 
}

Role Definitions

Role definitions, also known as permission levels, refer to a collection of base permissions that are grouped into a role. These definitions are typically assigned to SharePoint groups but can also be assigned to users. Some of the built-in permission levels are full control, contribute, approve, read, manage hierarchy, and design. The following code example demonstrates how you can easily verify the roles or permission level of a user:

using (SPWeb site = this.Web) 
{ 
    // Validate the page request to avoid 
    // any malicious posts. 
    if (Request.HttpMethod == "POST") 
    SPUtility.ValidateFormDigest(); 

    // Get a reference to the roles that 
    // are bound to the user and the role 
    // definition against which we need to 
    // verify the user. 
    SPRoleDefinitionBindingCollection usersRoles = 
      site.AllRolesForCurrentUser; 
    SPRoleDefinitionCollection siteRoleCollection = 
      site.RoleDefinitions; 
    SPRoleDefinition roleDefinition = 
      siteRoleCollection["Full Control"]; 
    // Determine whether the user is in the role. If 
    // not, redirect the user to the access-denied page 
    if (usersRoles.Contains(roleDefinition)) 
    { 
        //************************************ 
        // Check whether post back to run 
        // code that initiates the page. 
        if (IsPostBack == true) 
        { 
            // Execute application page logic. 
        } 
    } 
    else 
    { 
        Response.Redirect("/_layouts/accessdenied.aspx"); 
    } 
}

Group Membership

Group membership refers to the groups of which the user is a member. These often map directly to a role such as site administrators (full control) or site visitors (read), but you might be working with a custom group that was created specifically for your application pages to validate users against. The roles assigned to that custom group might not grant its members explicit rights to execute the actions your pages require, such as adding items to a lists or creating sites. Your application pages must verify the user's membership and execute such actions under a different set of permissions, perhaps by using the RunWithElevatedPrivileges method of the SPSecurity class. The SPGroup class exposes the necessary methods to verify group membership. In such a scenario your validation code might resemble the following:

using (SPWeb web = this.Web) 
{
    // Get a reference to the group 
    // against which we need to verify 
    // the user. 
    SPGroup customAppGroup = web.Groups["CITGO Intranet Members"];

    // Determine whether the user is a member of 
    // the group. If not, redirect the user 
    // to the access-denied page. 
    if (customAppGroup.ContainsCurrentUser) 
    { 

        // Validate the page request to avoid 
        // any malicious posts. 
        if (Request.HttpMethod == "POST") 
            SPUtility.ValidateFormDigest(); 

        SPSecurity.RunWithElevatedPrivileges(delegate() 
        { 
            // Execute application page logic. 
        }); 
    } 
    else 
    { 
        Response.Redirect("/_layouts/accessdenied.aspx"); 
    } 
}

Conclusion

Application pages are a critical part of the development framework of Windows SharePoint Services. If developed without proper care, application pages can expose security risks that are detrimental to sites, site collections, or even the entire farm. Fortunately, Microsoft has provided an extensive API that offers easy access to all the parts necessary to properly secure your pages. This article demonstrates how you can use the SharePoint API to validate page requests, base permissions, roles, and group membership to better secure your application pages.

Additional Resources

For more information, see the following resources: