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″ />
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);
}
}
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;
}
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.
No comments:
Post a Comment