SharePoint Internals – Hristo Pavlov’s Blog

21 May, 2008

What you need to know about AllowUnsafeUpdates (Part 2)

In short here is how to deal with AllowUnsafeUpdates:

1) Don’t update SharePoint objects from your code behind on GET requests as if you do so your code will be exploitable via a cross-site scripting. If you understand the consequences of doing this and still want to do it then see the first part of this article about how to use the AllowUnsafeUpdates property.

2) If your code is processing a POST request then make sure you call SPUtility.ValidateFormDigest() before you do anything else. This will ensure that the post request is validated (that it is not a cross-site scripting attack) and after that you will not have to worry about AllowUnsafeUpdates, because its default value will be “true” after the form digest is validated. To find out more about this read below.

So you have probably noticed from part one of the article that the internal method that sets the AllowUnsafeUpdates property had quite an interesting name: SetIgnoreCanary(). Canary is something that refers to a method of protecting from stack overflow attacks. The terminology is a reference to the historic practice of using canaries in coal mines, since they would be affected by toxic gases earlier than the miners, thus providing a biological warning system. In SharePoint the request canary is a unique pseudo-random value that protects you from cross-site scripting attacks. If you have ever examined the HTML source of your SharePoint pages you have probably noticed the __REQUESTDIGEST  hidden field.  This is what is referred to as the canary or the Form Digest and is used to verify that the request is genuine.

<input

    type=”hidden”

    name=”__REQUESTDIGEST”

    id=”__REQUESTDIGEST”

    value=”0x5DC31993EF285644A7C48F………..BFA2E6FB719CD7E9DB0922A329E97,19 May 2008 23:37:22 -0000″ />

As you see this is nothing more than a hidden field set by the server and verified back by the server when the page is submitted. As documented by Microsoft: The purpose of form digest validation is to help prevent security attacks where a user is tricked into posting data unknowingly to a server.

The place where the Form Digest value is set is the WebPartPage.FormOnLoad() method:

private void FormOnLoad(object sender, EventArgs e)

{

    if (HttpContext.Current != null)

    {

        SPWebcontextWeb = SPControl.GetContextWeb(HttpContext.Current);

        if(contextWeb != null)

        {

            SPWebPartManager.RegisterOWSScript(this, contextWeb);

            if (this.Page.Items["FormDigestRegistered"] == null)

            {

                stringbstrUrl = SPGlobal.GetVTIRequestUrl(this.Context.Request, null).ToString();

                SPStringCallback pFormCallback = new SPStringCallback();

                contextWeb.Request.RenderFormDigest(bstrUrl, pFormCallback);

                base.ClientScript.RegisterHiddenField(“__REQUESTDIGEST”, SPHttpUtility.NoEncode(pFormCallback.StringResult));

                FormDigest.RegisterDigestUpdateClientScriptBlockIfNeeded(this);

                this.Page.Items["FormDigestRegistered"] = true;

            }

        }

    }

}

 

The actual value of the __REQUESTDIGEST  field is generated by the COM objects in the OWSSVR.dll. After that another method is called: FormDigest.RegisterDigestUpdateClientScriptBlockIfNeeded()

public static void RegisterDigestUpdateClientScriptBlockIfNeeded(Page page)

{

    doubletotalMilliseconds;

 

    if (SPContext.Current.Site.WebApplication.FormDigestSettings.Enabled)

    {

        totalMilliseconds = SPContext.Current.Site.WebApplication.FormDigestSettings.Timeout.TotalMilliseconds;

        if(totalMilliseconds > 2147483647.0)

        {

            totalMilliseconds = 2147483647.0;

        }

    }

    else

    {

        return;

    }

 

    int num2 = Convert.ToInt32((double)(totalMilliseconds * 0.8));

    if(!page.ClientScript.IsOnSubmitStatementRegistered(typeof(FormDigest), “SPFormDigestUpdaterSubmitHandler”))

    {

      page.ClientScript.RegisterOnSubmitStatement(

            typeof(FormDigest),

            “SPFormDigestUpdaterSubmitHandler”,

            “UpdateFormDigest(‘” + SPEncode.ScriptEncode(SPContext.Current.Web.ServerRelativeUrl) + “‘, “+ num2.ToString(CultureInfo.InvariantCulture) + “);”);

 

        ScriptLink.Register(page, “init.js”, true, false);

    }

} 

There are a couple of interesting pieces of information in the code above. Firstly the Form Digest value generated by the server can expire. By default this happens in 30 min. Secondly the SPWebApplication.FormDigestSettings property can be used to change the form digest settings per web application. Those settings are persisted in the configuration database if you call SPWebApplication.Update(). The information provided in MSDN for the “Enabled” property is however not completely correct. MSDN says that: Enabled gets or sets a value that determines whether security validation is included with all form pages. But my SharePoint code examination and code tests showed that the Form Digest will be always included regardless of the Enabled value. The value of “false” means that when the digest expires (by default in 30 min) the user will not be able to submit the form and will get a security validation timeout exception trying to do so. Further test however showed that setting Enabled to false will indeed disable the security validation and you will not be getting the “The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.” exception even that AllowUnsafeUpdates will have a value of false.

Looking into the code of FormDigest.RegisterDigestUpdateClientScriptBlockIfNeeded() we see that it registers a client script that calls the UpdateFormDigest()  JavaScript function when the form is submitted. This JavaScript function calls the GetUpdatedFormDigest() method of the _vti_bin/sites.asmx WebService and updates the form digest field value on the fly before the form is submitted back to the server. According to the preliminary documentation of the Sites Web Service Protocol released by Microsoft on 4 Apr 2008: The GetUpdatedFormDigest is used to request renewal of an expired security validation, also known as a message digest. The purpose of form digest validation is to help prevent security attacks where a user is tricked into posting data unknowingly to a server.  To generate the new digest the web service simply creates a new instance of the FormDigest control and returns its DigestValue property value. So if the function is not called and the Form Digest is not updated a security timeout exception will occur and users will have to refresh a page before they can submitt it.

So the next question is where is the Form Digest validated? This is actually done in the SPWeb.ValidateFormDigest() method:

public bool ValidateFormDigest()

{

    HttpContext current = HttpContext.Current;

 

    if (current != null)

    {

        if (HttpContext.Current.Items["FormDigestValidated"] == null)

        {

            if (!this.Request.ValidateFormDigest(this.Url, null))

            {

                return false;

            }

 

            current.Items["FormDigestValidated"] = true;

 

            return true;

        }

 

        return true;

    }

 

    return true;

}

And as the code above shows the validation is done only once per web request and is then cached in the HTTPContext. Furthermore when a new SPWeb or SPSite object is created they also create an internal SPRequest object from the HTTPRequest. And within the lifetime of a single HTTP request to your web part or a web page, if the Form Digest has been successfully validated once then the AllowUnsafeUpdates property will now have a default value of true for all freshly created objects and the updates will be considered safe by SharePoint which also means you don’t have to set AllowUnsafeUpdates to do your job.

For some reason sometimes SharePoint doesn’t always call the ValidateFormDigest() method and this is why a workaround with setting AllowUnsafeUpdates to true is used. But a much better and safer solution is if you call this method yourself. The best way to do it is to call the SPUtility.ValidateFormDigest() method somewhere at the beginning of your POST request code behind.

And finally if you use in SharePoint you custom built ASPX pages which don’t inherit from a WebPartPage you can insert a FormDigest control in them which will insert the Form Digest  for you automatically and will protect you from cross-site scripting. Just make sure to call ValidateFormDigest() and DON’T touch the AllowUnsafeUpdates.

I hope that my investigation was more useful than confusing to you. Knowing more about how things work is always a key to building better and more secure applications.

Happy coding.

 

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.