diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 17875c2950..f1531ecb8d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; @@ -130,6 +131,11 @@ public class PreviewController : Controller [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] public ActionResult Frame(int id, string culture) { + if (ValidateProvidedCulture(culture) is false) + { + throw new InvalidOperationException($"Could not recognise the provided culture: {culture}"); + } + EnterPreview(id); // use a numeric URL because content may not be in cache and so .Url would fail @@ -138,6 +144,28 @@ public class PreviewController : Controller return RedirectPermanent($"../../{id}{query}"); } + private static bool ValidateProvidedCulture(string culture) + { + if (string.IsNullOrEmpty(culture)) + { + return true; + } + + // We can be confident the backoffice will have provided a valid culture in linking to the + // preview, so we don't need to check that the culture matches an Umbraco language. + // We are only concerned here with protecting against XSS attacks from a fiddled preview + // URL, so we can just confirm we have a valid culture. + try + { + CultureInfo.GetCultureInfo(culture, true); + return true; + } + catch (CultureNotFoundException) + { + return false; + } + } + public ActionResult? EnterPreview(int id) { IUser? user = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index 086a0b0c81..403e6324e1 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -141,7 +141,10 @@ public abstract class UmbracoViewPage : RazorPage string.Format( ContentSettings.PreviewBadge, HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoPath), - Context.Request.GetEncodedUrl(), + System.Web.HttpUtility.HtmlEncode(Context.Request.GetEncodedUrl()), // Belt and braces - via a browser at least it doesn't seem possible to have anything other than + // a valid culture code provided in the querystring of this URL. + // But just to be sure of prevention of an XSS vulnterablity we'll HTML encode here too. + // An expected URL is untouched by this encoding. UmbracoContext.PublishedRequest?.PublishedContent?.Id); } else