Security Best Practices for Developers in SharePoint 2010

Summary:  Learn about security best-practice recommendations when developing by using Microsoft SharePoint 2010.

Applies to: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio

Provided by:  Matt Swann, Microsoft Corporation

Contents

  • Introduction to Security Best Practices for SharePoint

  • SharePoint Security Best Practices: Cross-Site Scripting

  • SharePoint Security Best Practices: Cross-Site Request Forgery

  • SharePoint Security Best Practices: Elevation of Privilege

  • SharePoint Security Best Practices: Denial of Service

  • SharePoint Security Best Practices: Information Disclosure

  • Conclusion

  • Additional Resources

Introduction to Security Best Practices for SharePoint

This article provides a list of security best-practice recommendations to consider when you are developing by using Microsoft SharePoint 2010. This list addresses aspects of cross-site scripting (XSS), cross-site request forgery (CSRF or XSRF), elevation of privilege, denial of service, and information disclosure.

SharePoint Security Best Practices: Cross-Site Scripting

Cross-site scripting (XSS) attacks exploit vulnerabilities in webpage validation by injecting client-side script code. Common vulnerabilities that make your web applications susceptible to cross-site scripting attacks include failing to properly validate input, failing to encode output, and trusting the data retrieved from a shared database.

The following sections describe these vulnerabilities and provide recommendations. For more detailed information about XSS, see How To: Prevent Cross-Site Scripting in ASP.NET.

Encode Output Properly Using SPHttpUtility Methods

Attackers want to run their ECMAScript (JavaScript, JScript) script on your website because this enables them to steal authentication cookies and lure administrators into performing malicious actions.

These attackers look for places where user input is not encoded correctly, which allows the user input to be interpreted as HTML or JavaScript. For example, a list item that is tagged <script>alert()</script> would run JavaScript if it is not HTML-encoded by usingMicrosoft.SharePoint.Utilities.SPHttpUtility.HtmlEncode before being rendered on the client.

Recommendation

Before rendering user data on the client, encode it by using the appropriate method from the SPHttpUtility class. Sources of user data include the following:

  • Data from the SharePoint object model (for example, field values and list titles)

  • Parameters from the query string, headers, cookies, or form body in the request

  • Web service parameters

The encoding method you should use depends on how you are using the data on the client, as shown in Table 1.

Table 1. SPHttpUtility method to use for encoding

Type

Microsoft.SharePoint.Utilities.SPHttpUtility class method to use

HTML tag inner text

HtmlEncode

HTML tag inner text, allow basic formatting

HtmlEncodeAllowSimpleTextFormatting

HTML tag attribute, non-URL

HtmlEncode

HTML tag attribute, URL

HtmlUrlAttributeEncode

JavaScript

EcmaScriptStringLiteralEncode

Known safe values (for example, GUID from a query parameter)

NoEncode

Calling SPHttpUtility.NoEncode helps you audit your source code for encoding problems. By calling SPHttpUtility.NoEncode, you show you have considered whether this value had to be encoded.

Can you use the Microsoft .NET Framework HttpUtility encoders instead?

No—you should not. The .NET Framework HttpUtility encoding library does not encode all characters sufficiently. For example, SPHttpUtility in SharePoint encodes a single quotation mark as &#39; but .NET Framework HttpUtility does not encode the single quotation mark. This would result in an XSS bug, as shown in the following code.

Response.Write("<a href='http://contoso/" + 
HttpUtility.HtmlAttributeEncode(Request.QueryString["target"]) + 
"'>test</a>");

Never Allow Contributor Users to Add Script to the Site

Some features in SharePoint 2010 may enable users to author JavaScript on the site. For example:

  • The Content Editor Web Part can be used to add arbitrary JavaScript to a page.

  • Some document types may render as HTML in the browser when downloaded, allowing script to run.

Users who do not have the Add and Customize Pages permission are considered untrusted and must not be able to add script to the site by using these features that allow users to add script to the site. Users who have the Contribute permission level do not have this right.

Note

For more information about permissions in SharePoint 2010, see Chapter 11: Users and Permissions.

Recommendation

If your feature allows users to add script to the site, your feature must check the current user's permissions and block users who do not have SPBasePermissions.AddAndCustomizePages permission, as shown in the following code.

if (!SPContext.Current.Web.DoesUserHavePermissions(SPBasePermissions.AddAndCustomizePages))
{
   // User does not have permission to add script to the site. 
   // Either disable the feature or throw an access denied exception.
   throw new UnauthorizedAccessException();
}

If your feature displays or downloads user-uploaded documents, it must append the X-Content-Type-Options: nosniff HTTP response header when returning the document. If the document does not have to render in the browser, you must send two additional HTTP response headers to prevent this. The following are the HTTP response headers to send:

  • X-Download-Options: noopen

  • Content-Disposition: attachment

Always Set a Charset in the Content-Type HTTP Response Header

Developers often defend against XSS by blocking input that contains special characters such as angle brackets < and >. Unfortunately, attackers do not need to use angle brackets to inject a <script> tag if they can trick the browser into interpreting the page as UTF-7 or another charset.

Recommendation

Always specify a charset in the Content-Type HTTP response header. Usually, this should be UTF-8 format, as shown in the following example.

Content-Type: text/html; charset=UTF-8

This specification of a charset prevents an attacker from controlling the browser's charset from within the content of the page.

In addition to specifying the charset, it is good practice to specify the nosniff header to prevent MIME sniffing.

Do Not Allow User Provided Values in Style and Event Attributes

Even though it is bad practice to allow user provided values in style and event attributes, it happens from time to time. We recommend that you do the following with user-provided data.

Recommendation

Do not reflect user-provided value in style attributes. Even if you use HTML encoding, it does not help. Mitigation when using HTML encode is case by case, as shown in the following examples.

Example 1: The following entire style attribute value is encoded, but it still runs script because all browsers HTML decode attribute values before consuming them. In this case, HTML encode does not help, and there is no reasonable way to mitigate its vulnerability.

<html>
  <body>
     <div style="&#119;&#58;&#101;&#120;&#112;&#114;&#101;&#115;&#115;&#105;&#111;&#110;&#40;&#97;&#108;&#101;&#114;&#116;&#40;&#41;&#41;"></div>
  </body>
</html>

Example 2: For font-family, you may only want to allow font names that are known to be safe. For background-image, you may need to validate the protocol handler used and also ensure the URL is checked to verify that no new style token injection has occurred.

Do not reflect user provided value in event attributes. Again, even if you use HTML encode, it does not help. Mitigation when using HTML encode is case by case.

Instead, you should use JavaScript encoding and in some cases encode the user-provided data twice. The following are some examples.

Example 1: For the following, JavaScript encoding is sufficient.

<a href="#" onclick="alert(__USER_PROVIDED_DATA__)">

Example 2: For the following, data should first be HTML encoded, and then JavaScript encoded.

<a href="#" onclick="document.write(__USER_PROVIDED_DATA_)">

Example 3: The following is inherently vulnerable. There is no reasonable way to mitigate its vulnerability.

<a href="#" onclick="__USER_PROVIDE_DATA__">

SharePoint Security Best Practices: Cross-Site Request Forgery

Cross-site request forgery (CSRF or XSRF) is an attack that tricks the victim's browser into performing an unwanted action on the victim's behalf. For example, this type of attack could result in transferring funds, changing a password, or purchasing an item.

For more information about CSRF, see Cross-Site Request Forgery Attack Explained.

Validate the Form Digest Canary Before Processing a Postback

An attacker can post to pages in SharePoint even if the attacker is not able to run script in the domain. If you browse to a page owned by the attacker, he could perform operations on SharePoint using your credentials.

For example, the attacker might control a page at http://contoso123. When you browse to his page, it posts to http://wingtip/_layouts/deleteweb.aspx using your credentials. If you have administrator's permissions on http://wingtip, this would delete the http://wingtip website.

Recommendation

SharePoint uses a dynamic canary to ensure that POST requests come from the same domain as the server.

You must do two things:

  • Send the canary with every postback or web service request.

  • Validate the canary before acting on the postback or web service request.

The following are the steps to take.

  1. Send the canary with every postback or web service request.

    The canary value is already present in a hidden __REQUESTDIGEST form element on every page that uses the SharePoint master page. It is automatically sent with every postback.

    If you do not inherit from the SharePoint master page, you must use the Microsoft.SharePoint.WebControls.FormDigest control to write the value into the page. If you are making a web service call, you must get the __REQUESTDIGEST value and include it in the X-RequestDigest HTTP request header, as shown in the following example.

    var canaryValue = document.getElementById('__REQUESTDIGEST').value;
    request.SetRequestHeader("X-RequestDigest", canaryValue);
    

    Client applications (outside the browser) must manually fetch the canary value by calling the GetUpdatedFormDigestInformation web method on the Sites.asmx SOAP web service. This value should be included in the X-RequestDigest header of any web service call.

    Note

    The canary value times out after 30 minutes (configurable, returned by GetUpdatedFormDigestInformation), so client applications must be prepared to re-request a valid canary if the server rejects it as too old. This is not an issue in the browser—the FormDigest control uses JavaScript to refresh the __REQUESTDIGEST form value appropriately.

  2. Validate the canary before acting on the postback or web service request.

    Before performing any action as a result of a postback or a web service call, you must call ValidateFormDigest() to verify that the canary is valid. It throws an exception if the canary is not valid.

    Note

    This includes non-state-changing postbacks; for example, refreshing a page with user-submitted content.

Avoid Using AllowUnsafeUpdates Where Possible

SharePoint 2010 prevents developers from performing state-changing operations on a GET request. For example, a Microsoft ASP.NET page is not allowed to update the contents of a list item or web property when the list item or web property is fetched by using a GET.

In conjunction with the __REQUESTDIGEST canary, this prevents CSRF attacks. However, some features may need to perform lazy initialization.

Recommendation

If your feature must perform a state-changing operation on a GET request, you should first consider whether the feature is behaving properly. It is preferable to perform state-changing operations only on a POST request, because the __REQUESTDIGEST canary defends your feature against CSRF attacks.

If your feature's design mandates that a state-changing operation must occur on a GET request, you can disable this check by setting the AllowUnsafeUpdates property of the current Microsoft.SharePoint.SPWeb class to true. Remember to reset it after performing your operation, and use a try-catch-finally block to ensure that an exception does not leave it set to true, as shown in the following example.

try
{
   SPContext.Current.Web.AllowUnsafeUpdates = true;
   // State-changing operation occurs here.
}
catch
{
   // Handle or re-throw an exception.
}
finally
{
   SPContext.Current.Web.AllowUnsafeUpdates = false;
}

AllowUnsafeUpdates must never be used to work around a canary validation error. If you are receiving a canary validation error on a postback or web service request, you should send and validate the canary properly.

Use SPUtility to Redirect to a Different Page

Forms often redirect to a different page after the user clicks OK or Cancel. The URL of this page is often passed in a query parameter (for example, a Source parameter). If this URL is not validated, attackers can use this parameter to redirect the user to a potentially malicious location.

Recommendation

Call the Microsoft.SharePoint.Utilities.SPUtility.Redirect method to redirect the user to another page. The Redirect method ensures that the destination URL is on the current domain, and optionally can ensure that the redirect goes to another _layouts page on the current web page.

SharePoint Security Best Practices: Elevation of Privilege

Elevation of privilege results from giving an attacker authorization permissions beyond those that are initially granted. For example, an attacker with a privilege set of "read only" permissions somehow elevates the set to include "read and write."

For more information, see Elevation of Privilege.

Check User Permissions Appropriately

In some cases, your feature may not automatically check permissions. For example:

  • Microsoft.SharePoint.SPSecurity.Elevated RunWithElevatedPrivileges operations always succeed regardless of the user's permissions.

  • Some operations may not do a permission check if they do not use the SharePoint object model.

In both of these previous these cases, your feature must manually check permissions.

Recommendation

First, determine which Microsoft.SharePoint.SecurableObject—an SPListItem object, SPList object, or SPWeb object—you should check permissions on. If your feature affects the web, your securable object is the current context web. If your feature operates on a per-list basis, your securable object is the current context list.

Next, determine whether your feature should take a special action if the user does not have permissions (like hiding some user-interface elements) or whether it should deny access.

  • If your feature should take action based on the user's permissions, call DoesUserHavePermissions on your securable object and use the return value.

    Note

    The DoesUserHavePermissions method does not throw an exception if the user does not have the required permissions. It simply returns false.

  • If your feature should throw an access-denied exception, call Microsoft.SharePoint.SPSecurableObject.CheckPermissions on the securable object. It throws an access-denied exception and appropriately returns this information to the user.

Construct SPSite Objects Safely

The Microsoft.SharePoint.SPSite constructor is prone to the following two problems:

  • New SPSite objects can be constructed by using a fully qualified domain name, for example http://contoso1.example.com. If this domain name is different from the current request context, it can lead to a cross-domain security issue.

  • New SPSite objects can be constructed by using a site identifier (ID) and optional user token, but without a fully qualified URL or Microsoft.SharePoint.Administration.SPUrlZone enumeration. If the current request context is not the Default zone, this can bypass web application policy.

Recommendation

You must not construct an SPSite object by using only the site ID and user token. Instead, pass the Microsoft.SharePoint.Administration.SPUrlZone enumerator value or the fully qualified domain name of the site.

If you are constructing a new SPSite object by using a GUID or fully qualified domain name that came from the user, you must call Microsoft.SharePoint.SPSite.ValidateDomainCompatibility. This ensures that the domain name of the new SPSite is identical to the domain name of the current request context, as shown in the following example.

String siteUrl = Page.Request.QueryString["site"]; 
// This can be a fully qualified domain names (FQDN), for example,
// http://contoso/sites/example.
 
if (!SPSite.ValidateDomainCompatibility(SPContext.Current.Site.Url, siteUrl))
{
   // If the domain of siteUrl differs from the current context, block cross-domain operations.
   throw new UnauthorizedAccessException();
}

Additional Information Constructing SPSite Objects Safely

Script on one domain (for example, https://contoso.com) is not allowed to access data on other domains (for example, http://wingtip.com). This is known as the same-origin policy.

However, the SharePoint object model can access both domains if they are hosted on the same farm. Attackers can exploit this to access data on a different domain if you do not block this by using Microsoft.SharePoint.SPSite.ValidateDomainCompatibility.

Separately, farm administrators can use web application policy to give users different privileges on different zones of a website. For example, policy may grant users Full Control on the Default zone of a site, but only Read access to the Internet zone.

If the zone information is not passed to the SPSite constructor by using a Microsoft.SharePoint.Administration.SPUrlZone enumerator value or an FQDN, the object model assumes that the Default zone is intended. This can lead to elevation of privilege issues in the scenario described earlier in this section.

Carefully Restrict Server-Side HTTP Requests

Some features may cause the server to make outgoing HTTP requests on behalf of the user. For example, Microsoft Business Connectivity Services (BCS) allows users to specify database and web service URLs to connect to.

Because these requests originate from the server running SharePoint that is behind the firewall, these requests can be used to connect to or attack other computers in the data center.

Recommendation

Do not allow users to specify arbitrary URLs for SharePoint to connect to. Instead, allow farm administrators to configure a list of URLs that are safe.

Alternatively, resolve each URL entered by the user to its canonical Internet Protocol (IP) address and give farm administrators the ability to block network ranges by IP and subnet mask (for example, 192.168.0.0/255.255.0.0).

Elevated Objects Must Remain Inside a RunWithElevatedPrivileges Block

Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges is used to run a block of code by using service account credentials. This is commonly used to perform an action on behalf of a user when that user does not have permission to perform the action directly.

For example, a user may not have permission to provision new site collections, but SharePoint must provision a site for the user. By provisioning the site collection in a RunWithElevatedPrivileges block, the action succeeds.

In general, RunWithElevatedPrivileges is used as follows.

SPSecurity.RunWithElevatedPrivileges(delegate()
{
   using (SPSite elevatedSite = new SPSite(SPContext.Current.Site.Id))
   {
      using (SPWeb elevatedWeb = elevatedSite.OpenWeb(SPContext.Current.Web.Id))
      {
         // Perform administrative actions by using the elevated site and web objects.
      }
   }
});

SharePoint object model objects that are created or accessed by using the elevated Microsoft.SharePoint.SPSite object and Microsoft.SharePoint.SPWeb object retain the permissions they were created with. Returning these objects outside the RunWithElevatedPrivileges block can lead to privilege elevation issues.

Recommendation

SharePoint object model objects created or accessed within a Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges object must not be returned outside of the RunWithElevatedPrivileges block.

Additional information

When you instantiate an SPSite object, SharePoint backs that object by an underlying SPRequest object in native code. The SPRequest object remembers which user instantiated the object. SPRequest is shared by all objects that are accessed via that SPSite object.

Note

For more information about the SPRequest object, see Disposing Objects.

When an SPSite object is provisioned in a RunWithElevatedPrivileges block, the SPRequest object notes that the current user is the System Account. If the SPSite object is used to access a Microsoft.SharePoint.SPListItem object, that list item shares the same SPRequest object—it is an "elevated" object just like the SPSite object.

If the SPListItem object is passed outside of the RunWithElevatedPrivileges block, it retains its underlying SPRequest object and continues to be elevated. Code that expects to be running under the current user's credentials will have privilege elevation problems if it uses this SPListItem object.

SharePoint Security Best Practices: Denial of Service

Denial of service occurs when a system is overwhelmed in such a way that messages cannot be processed, or they are processed extremely slowly. For more information, see Denial of Service.

Limit the Amount of Work That Can Be Done on a Single Request

Some features parse user input and perform an action for each element in the request. If the feature does not limit the number of elements it parsed, this can lead to a denial-of-service attack.

Recommendation

Enforce a reasonable upper bound for the amount of user input that your feature processes on a single request.

SharePoint Security Best Practices: Information Disclosure

Information disclosure enables an attacker to gain valuable information about a system. Therefore, always consider what information you are revealing and whether it can be used by a malicious user.

For more information, see Information Disclosure.

Scrub Web Service Exceptions Before Returning Them to Callers

A web service call may throw an exception that returns too much data about the server, such as internal server names or file system paths.

Attackers can use this information to profile the SharePoint farm and launch a targeted attack.

Recommendation

When implementing a SOAP web method, catch all exceptions and pass them to SoapServerException.HandleException before returning them to the user, as shown in the following example.

try 
      {
          return new SoapXml.SoapXmlElement(
              m_ListSchemaImpl.GetListAndView(listName, viewName));
      } 
      catch (Exception e) 
      {
          throw SoapServerException.HandleException(e);
 
      }

Conclusion

In this article, you learn about security best-practice recommendations when you are developing solutions by using SharePoint 2010. The recommendations address aspects of cross-site scripting (XSS), cross-site request forgery (CSRF or XSRF), elevation of privilege, denial of service, and information disclosure.

Additional Resources

For more information, see the following resources: