Make HtmlTags safer, properly sanitize in the view as well

This commit is contained in:
nicolas.dorier
2025-01-19 10:59:28 +09:00
parent 1830d398a4
commit d3d6b2d15c
6 changed files with 34 additions and 25 deletions

View File

@@ -35,33 +35,35 @@ namespace BTCPayServer.Abstractions.Services
return _htmlHelper.Raw(_jsonHelper.Serialize(model)); return _htmlHelper.Raw(_jsonHelper.Serialize(model));
} }
public IHtmlContent Meta(string inputHtml) => _htmlHelper.Raw(RawMeta(inputHtml, out _));
public string RawMeta(string inputHtml, out bool isHtmlModified) public string RawMeta(string inputHtml, out bool isHtmlModified)
{ {
bool bHtmlModified; bool bHtmlModified;
HtmlSanitizer _metaSanitizer = new HtmlSanitizer(); HtmlSanitizer sane = new HtmlSanitizer();
_metaSanitizer.AllowedTags.Clear(); sane.AllowedTags.Clear();
_metaSanitizer.AllowedTags.Add("meta"); sane.AllowedTags.Add("meta");
_metaSanitizer.AllowedAttributes.Clear(); sane.AllowedAttributes.Clear();
_metaSanitizer.AllowedAttributes.Add("name"); sane.AllowedAttributes.Add("name");
_metaSanitizer.AllowedAttributes.Add("http-equiv"); sane.AllowedAttributes.Add("http-equiv");
_metaSanitizer.AllowedAttributes.Add("content"); sane.AllowedAttributes.Add("content");
_metaSanitizer.AllowedAttributes.Add("value"); sane.AllowedAttributes.Add("value");
_metaSanitizer.AllowedAttributes.Add("property"); sane.AllowedAttributes.Add("property");
_metaSanitizer.AllowDataAttributes = false; sane.AllowDataAttributes = false;
_metaSanitizer.RemovingTag += (sender, e) => bHtmlModified = true; sane.RemovingTag += (sender, e) => bHtmlModified = true;
_metaSanitizer.RemovingAtRule += (sender, e) => bHtmlModified = true; sane.RemovingAtRule += (sender, e) => bHtmlModified = true;
_metaSanitizer.RemovingAttribute += (sender, e) => bHtmlModified = true; sane.RemovingAttribute += (sender, e) => bHtmlModified = true;
_metaSanitizer.RemovingComment += (sender, e) => bHtmlModified = true; sane.RemovingComment += (sender, e) => bHtmlModified = true;
_metaSanitizer.RemovingCssClass += (sender, e) => bHtmlModified = true; sane.RemovingCssClass += (sender, e) => bHtmlModified = true;
_metaSanitizer.RemovingStyle += (sender, e) => bHtmlModified = true; sane.RemovingStyle += (sender, e) => bHtmlModified = true;
bHtmlModified = false; bHtmlModified = false;
var sRet = _metaSanitizer.Sanitize(inputHtml); var sRet = sane.Sanitize(inputHtml);
isHtmlModified = bHtmlModified; isHtmlModified = bHtmlModified;
return sRet; return sRet;

View File

@@ -6,6 +6,7 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
@@ -38,13 +39,16 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly IFileService _fileService; private readonly IFileService _fileService;
public Safe Safe { get; }
public GreenfieldAppsController( public GreenfieldAppsController(
AppService appService, AppService appService,
UriResolver uriResolver, UriResolver uriResolver,
StoreRepository storeRepository, StoreRepository storeRepository,
CurrencyNameTable currencies, CurrencyNameTable currencies,
IFileService fileService, IFileService fileService,
UserManager<ApplicationUser> userManager UserManager<ApplicationUser> userManager,
Safe safe
) )
{ {
_appService = appService; _appService = appService;
@@ -53,6 +57,7 @@ namespace BTCPayServer.Controllers.Greenfield
_currencies = currencies; _currencies = currencies;
_fileService = fileService; _fileService = fileService;
_userManager = userManager; _userManager = userManager;
Safe = safe;
} }
[HttpPost("~/api/v1/stores/{storeId}/apps/crowdfund")] [HttpPost("~/api/v1/stores/{storeId}/apps/crowdfund")]
@@ -305,7 +310,8 @@ namespace BTCPayServer.Controllers.Greenfield
var parsedSounds = ValidateStringArray(request.Sounds); var parsedSounds = ValidateStringArray(request.Sounds);
var parsedColors = ValidateStringArray(request.AnimationColors); var parsedColors = ValidateStringArray(request.AnimationColors);
Enum.TryParse<BTCPayServer.Services.Apps.CrowdfundResetEvery>(request.ResetEvery.ToString(), true, out var resetEvery); Enum.TryParse<BTCPayServer.Services.Apps.CrowdfundResetEvery>(request.ResetEvery.ToString(), true, out var resetEvery);
if (request.HtmlMetaTags is not null)
request.HtmlMetaTags = Safe.RawMeta(request.HtmlMetaTags, out _);
return new CrowdfundSettings return new CrowdfundSettings
{ {
Title = request.Title?.Trim() ?? request.AppName, Title = request.Title?.Trim() ?? request.AppName,
@@ -342,6 +348,8 @@ namespace BTCPayServer.Controllers.Greenfield
private PointOfSaleSettings ToPointOfSaleSettings(PointOfSaleAppRequest request, PointOfSaleSettings settings) private PointOfSaleSettings ToPointOfSaleSettings(PointOfSaleAppRequest request, PointOfSaleSettings settings)
{ {
Enum.TryParse<BTCPayServer.Plugins.PointOfSale.PosViewType>(request.DefaultView.ToString(), true, out var defaultView); Enum.TryParse<BTCPayServer.Plugins.PointOfSale.PosViewType>(request.DefaultView.ToString(), true, out var defaultView);
if (request.HtmlMetaTags is not null)
request.HtmlMetaTags = Safe.RawMeta(request.HtmlMetaTags, out _);
return new PointOfSaleSettings return new PointOfSaleSettings
{ {

View File

@@ -588,7 +588,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
}); });
if (wasHtmlModified) if (wasHtmlModified)
{ {
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value;
} }
else else
{ {

View File

@@ -729,7 +729,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
await _appService.UpdateOrCreateApp(app); await _appService.UpdateOrCreateApp(app);
if (wasHtmlModified) if (wasHtmlModified)
{ {
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value;
} else { } else {
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
} }

View File

@@ -35,8 +35,7 @@
object-fit: scale-down; object-fit: scale-down;
} }
</style> </style>
@* Html.Raw OK here since Html has been cleaned before in controller *@ @this.Safe.Meta(Model.HtmlMetaTags)
@Html.Raw(Model.HtmlMetaTags)
<vc:ui-extension-point location="crowdfund-head" model="@Model"/> <vc:ui-extension-point location="crowdfund-head" model="@Model"/>
</head> </head>
<body class="min-vh-100 p-2"> <body class="min-vh-100 p-2">

View File

@@ -5,6 +5,7 @@
@inject IWebHostEnvironment WebHostEnvironment @inject IWebHostEnvironment WebHostEnvironment
@inject SettingsRepository SettingsRepository @inject SettingsRepository SettingsRepository
@inject BTCPayServerEnvironment Env @inject BTCPayServerEnvironment Env
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel @model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{ @{
ViewData["Title"] = string.IsNullOrEmpty(Model.Title) ? Model.StoreName : Model.Title; ViewData["Title"] = string.IsNullOrEmpty(Model.Title) ? Model.StoreName : Model.Title;
@@ -40,8 +41,7 @@
<link rel="apple-touch-startup-image" href="~/img/splash.png"> <link rel="apple-touch-startup-image" href="~/img/splash.png">
<link rel="manifest" href="@(await GetDynamicManifest(ViewData["Title"]!.ToString()))"> <link rel="manifest" href="@(await GetDynamicManifest(ViewData["Title"]!.ToString()))">
<link href="~/pos/common.css" asp-append-version="true" rel="stylesheet" /> <link href="~/pos/common.css" asp-append-version="true" rel="stylesheet" />
@* Html.Raw OK here since Html has been cleaned before in controller *@ @this.Safe.Meta(Model.HtmlMetaTags)
@Html.Raw(Model.HtmlMetaTags)
@await RenderSectionAsync("PageHeadContent", false) @await RenderSectionAsync("PageHeadContent", false)
</head> </head>
<body class="min-vh-100"> <body class="min-vh-100">