Skip to content
Permalink
Browse files

Fixes U4-7459 XSRF protection bypass - ensures tokens are checked for…

… the non-editor api controllers
  • Loading branch information...
Shazwazza committed Nov 25, 2015
1 parent 924a016 commit 18c3345e47663a358a042652e697b988d6a380eb
@@ -357,4 +357,20 @@

});

//This sets the default jquery ajax headers to include our csrf token, we
// need to user the beforeSend method because our token changes per user/login so
// it cannot be static
$.ajaxSetup({
beforeSend: function (xhr) {

function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
}

xhr.setRequestHeader("X-XSRF-TOKEN", getCookie("XSRF-TOKEN"));
}
});

})(jQuery);
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Web.Mvc;
using Umbraco.Web.WebApi.Filters;

namespace Umbraco.Web.Mvc
{
/// <summary>
/// A filter to check for the csrf token based on Angular's standard approach
/// </summary>
/// <remarks>
/// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/
///
/// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled
/// </remarks>
public sealed class ValidateMvcAngularAntiForgeryTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity;
if (userIdentity != null)
{
//if there is not CookiePath claim, then exist
if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false)
{
base.OnActionExecuting(filterContext);
return;
}
}

string failedReason;
var headers = new List<KeyValuePair<string, List<string>>>();
foreach (var key in filterContext.HttpContext.Request.Headers.AllKeys)
{
if (headers.Any(x => x.Key == key))
{
var found = headers.First(x => x.Key == key);
found.Value.Add(filterContext.HttpContext.Request.Headers[key]);
}
else
{
headers.Add(new KeyValuePair<string, List<string>>(key, new List<string> { filterContext.HttpContext.Request.Headers[key] }));
}
}
var cookie = filterContext.HttpContext.Request.Cookies[AngularAntiForgeryHelper.CsrfValidationCookieName];
if (AngularAntiForgeryHelper.ValidateHeaders(
headers.Select(x => new KeyValuePair<string, IEnumerable<string>>(x.Key, x.Value)).ToArray(),
cookie == null ? "" : cookie.Value,
out failedReason) == false)
{
var result = new HttpStatusCodeResult(HttpStatusCode.ExpectationFailed);
filterContext.Result = result;
return;
}

base.OnActionExecuting(filterContext);
}
}
}
@@ -306,6 +306,7 @@
<Compile Include="Models\PublishedContentWithKeyBase.cs" />
<Compile Include="Mvc\IRenderController.cs" />
<Compile Include="Mvc\RenderIndexActionSelectorAttribute.cs" />
<Compile Include="Mvc\ValidateMvcAngularAntiForgeryTokenAttribute.cs" />
<Compile Include="PropertyEditors\DatePreValueEditor.cs" />
<Compile Include="RequestLifespanMessagesFactory.cs" />
<Compile Include="Scheduling\LatchedBackgroundTaskBase.cs" />
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -28,6 +30,8 @@ public static class AngularAntiForgeryHelper
/// </summary>
public const string AngularHeadername = "X-XSRF-TOKEN";



/// <summary>
/// Returns 2 tokens - one for the cookie value and one that angular should set as the header value
/// </summary>
@@ -64,13 +68,10 @@ public static bool ValidateTokens(string cookieToken, string headerToken)
return true;
}

/// <summary>
/// Validates the headers/cookies passed in for the request
/// </summary>
/// <param name="requestHeaders"></param>
/// <param name="failedReason"></param>
/// <returns></returns>
public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason)
internal static bool ValidateHeaders(
KeyValuePair<string, IEnumerable<string>>[] requestHeaders,
string cookieToken,
out string failedReason)
{
failedReason = "";

@@ -85,26 +86,40 @@ public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string
.Select(z => z.Value)
.SelectMany(z => z)
.FirstOrDefault();

var cookieToken = requestHeaders
.GetCookies()
.Select(c => c[CsrfValidationCookieName])
.FirstOrDefault();


// both header and cookie must be there
if (cookieToken == null || headerToken == null)
{
failedReason = "Missing token null";
return false;
}

if (ValidateTokens(cookieToken.Value, headerToken) == false)
if (ValidateTokens(cookieToken, headerToken) == false)
{
failedReason = "Invalid token";
return false;
}

return true;
}

/// <summary>
/// Validates the headers/cookies passed in for the request
/// </summary>
/// <param name="requestHeaders"></param>
/// <param name="failedReason"></param>
/// <returns></returns>
public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason)
{
var cookieToken = requestHeaders
.GetCookies()
.Select(c => c[CsrfValidationCookieName])
.FirstOrDefault();

return ValidateHeaders(
requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(),
cookieToken == null ? null : cookieToken.Value,
out failedReason);
}
}
}
@@ -15,6 +15,7 @@ namespace Umbraco.Web.WebServices
/// <summary>
/// A REST controller used for the publish dialog in order to publish bulk items at once
/// </summary>
[ValidateMvcAngularAntiForgeryToken]
public class BulkPublishController : UmbracoAuthorizedController
{
/// <summary>
@@ -13,13 +13,15 @@
//using umbraco.cms.businesslogic.language;
using umbraco.BusinessLogic.Actions;
using umbraco.cms.businesslogic.web;
using Umbraco.Web.WebApi.Filters;

namespace Umbraco.Web.WebServices
{
/// <summary>
/// A REST controller used for managing domains.
/// </summary>
/// <remarks>Nothing to do with Active Directory.</remarks>
[ValidateAngularAntiForgeryToken]
public class DomainsApiController : UmbracoAuthorizedApiController
{
[HttpPost]
@@ -13,9 +13,11 @@
using Umbraco.Core.Logging;
using Umbraco.Web.Search;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;

namespace Umbraco.Web.WebServices
{
[ValidateAngularAntiForgeryToken]
public class ExamineManagementApiController : UmbracoAuthorizedApiController
{
/// <summary>
@@ -26,6 +26,7 @@ namespace Umbraco.Web.WebServices
/// This isn't fully implemented yet but we should migrate all of the logic in the umbraco.presentation.webservices.codeEditorSave
/// over to this controller.
/// </remarks>
[ValidateMvcAngularAntiForgeryToken]
public class SaveFileController : UmbracoAuthorizedController
{
/// <summary>
@@ -4,10 +4,11 @@
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;

namespace Umbraco.Web.WebServices
{

[ValidateAngularAntiForgeryToken]
public class XmlDataIntegrityController : UmbracoAuthorizedApiController
{
[HttpPost]

0 comments on commit 18c3345

Please sign in to comment.
You can’t perform that action at this time.