CSP: Remove unsafe-eval when vue isn't used (#4747)

* CSP: Remove unsafe-eval when vue isn't used

* Prevent XSS injection via VueJS
This commit is contained in:
Nicolas Dorier 2023-03-08 17:57:36 +09:00 committed by GitHub
parent 2010a9a458
commit 7b5ce8f70c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 88 additions and 56 deletions

View File

@ -114,6 +114,11 @@ namespace BTCPayServer.Security
_Policies.Add(policy);
}
public void UnsafeEval()
{
Add("script-src", "'unsafe-eval'");
}
public IEnumerable<ConsentSecurityPolicy> Rules => _Policies;
public bool HasRules => _Policies.Count != 0;

View File

@ -24,7 +24,7 @@ namespace BTCPayServer.Filters
AutoSelf = false;
FixWebsocket = false;
UnsafeInline = false;
ScriptSrc = "'self' 'unsafe-eval'"; // unsafe-eval needed for vue
ScriptSrc = "'self'";
}
}

View File

@ -3,13 +3,14 @@
@inject BTCPayServer.Services.BTCPayServerEnvironment Env
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@{
ViewData["Title"] = Model.Title;
Layout = null;
if (!string.IsNullOrEmpty(Model.DisqusShortname))
{
Csp.Add("script-src", $"https://{Model.DisqusShortname}.disqus.com");
Csp.Add("script-src", "https://c.disquscdn.com");
}
ViewData["Title"] = Model.Title;
Layout = null;
Csp.UnsafeEval();
if (!string.IsNullOrEmpty(Model.DisqusShortname))
{
Csp.Add("script-src", $"https://{Model.DisqusShortname}.disqus.com");
Csp.Add("script-src", "https://c.disquscdn.com");
}
}
<!DOCTYPE html>
<html class="h-100" @(Env.IsDeveloping ? " data-devenv" : "")>
@ -55,13 +56,13 @@
<div class="public-page-wrap flex-column container" id="app" @(Model.SimpleDisplay ? "" : "v-cloak")>
@if (!string.IsNullOrEmpty(Model.MainImageUrl))
{
<img v-if="srvModel.mainImageUrl" src="@Model.MainImageUrl" :src="srvModel.mainImageUrl" alt="@Model.Title" :alt="srvModel.title" id="crowdfund-main-image" asp-append-version="true"/>
<img v-if="srvModel.mainImageUrl" :src="srvModel.mainImageUrl" :alt="srvModel.title" id="crowdfund-main-image" asp-append-version="true"/>
}
<div class="d-flex flex-column justify-content-between p-3 text-center" id="crowdfund-header-container">
<h1 class="mb-3">@Model.Title</h1>
<h1 class="mb-3">{{ srvModel.title }}</h1>
@if (!string.IsNullOrEmpty(Model.Tagline))
{
<h2 class="h3 mb-3 fw-semibold" v-if="srvModel.tagline" v-text="srvModel.tagline">@Model.Tagline</h2>
<h2 class="h3 mb-3 fw-semibold" v-if="srvModel.tagline" v-text="srvModel.tagline"></h2>
}
@if (Model.TargetAmount.HasValue)
{
@ -221,7 +222,6 @@
<b-tabs>
<b-tab title="Details" active>
<div class="overflow-hidden pt-3" v-html="srvModel.description" id="crowdfund-body-description">
@Safe.Raw(Model.Description)
</div>
</b-tab>
<b-tab title="Discussion">
@ -231,7 +231,6 @@
</template>
<template v-else>
<div class="overflow-hidden" v-html="srvModel.description" id="crowdfund-body-description">
@Safe.Raw(Model.Description)
</div>
</template>
</div>
@ -246,7 +245,7 @@
</contribute>
</div>
</div>
<noscript>
<noscript v-pre>
<div class="row justify-content-between">
<div class="col-md-7 col-sm-12">
<div class="overflow-hidden">@Safe.Raw(Model.Description)</div>

View File

@ -4,9 +4,11 @@
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, "Update Crowdfund", Model.AppId);
Csp.UnsafeEval();
}
@section PageHeadContent {

View File

@ -1,7 +1,9 @@
@using BTCPayServer.Views.Stores
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.PayButton.Models.PayButtonViewModel
@{
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
Csp.UnsafeEval();
}
@section PageHeadContent {

View File

@ -1,6 +1,8 @@
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{
Layout = "PointOfSale/Public/_Layout";
Csp.UnsafeEval();
}
@section PageHeadContent {
<style>

View File

@ -5,10 +5,11 @@
@using BTCPayServer.Forms
@using BTCPayServer.Services.Stores
@inject FormDataService FormDataService
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.PointOfSale.Models.UpdatePointOfSaleViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, "Update Point of Sale", Model.Id);
Csp.UnsafeEval();
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);
}

View File

@ -8,7 +8,7 @@
}
}
<table class="table my-0">
<table class="table my-0" v-pre>
@foreach (var (key, value) in Model.Items)
{
<tr>

View File

@ -8,7 +8,7 @@
foreach (var error in errors.Errors)
{
<br/>
<span class="text-danger">@error.ErrorMessage</span>
<span class="text-danger" v-pre>@error.ErrorMessage</span>
}
}

View File

@ -27,7 +27,7 @@
@await RenderSectionAsync("PageHeadContent", false)
</head>
<body class="d-flex flex-column flex-lg-row min-vh-100">
<header id="mainMenu" class="btcpay-header d-flex flex-column">
<header id="mainMenu" class="btcpay-header d-flex flex-column" v-pre>
<div id="mainMenuHead">
<button id="mainMenuToggle" class="mainMenuButton" type="button" data-bs-toggle="offcanvas" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span>Menu</span>

View File

@ -1,4 +1,4 @@
@{
@{
Layout = "_LayoutSimple";
ViewBag.ShowTitle ??= true;
ViewBag.ShowLeadText ??= false;
@ -50,7 +50,7 @@
<div class="account-form">
@if (ViewBag.ShowTitle)
{
<h4>@ViewData["Title"]</h4>
<h4 v-pre>@ViewData["Title"]</h4>
}
@RenderBody()
</div>

View File

@ -5,7 +5,7 @@
@if (parsedModel != null)
{
<div class="alert alert-@parsedModel.SeverityCSS @(parsedModel.AllowDismiss? "alert-dismissible":"" ) @(ViewData["Margin"] ?? "mb-4") text-break" role="alert">
<div class="alert alert-@parsedModel.SeverityCSS @(parsedModel.AllowDismiss? "alert-dismissible":"" ) @(ViewData["Margin"] ?? "mb-4") text-break" role="alert" v-pre>
@if (parsedModel.AllowDismiss)
{
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">

View File

@ -7,7 +7,7 @@
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.LogoFileId)
: null;
}
<header class="store-header">
<header class="store-header" v-pre>
@if (!string.IsNullOrEmpty(logoUrl))
{
<img src="@logoUrl" alt="@Model.Title" class="store-logo"/>

View File

@ -1,8 +1,10 @@
@model LoginViewModel
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@inject BTCPayServer.Services.PoliciesSettings PoliciesSettings
@{
ViewData["Title"] = "Sign in";
Layout = "_LayoutSignedOut";
ViewData["Title"] = "Sign in";
Layout = "_LayoutSignedOut";
Csp.UnsafeEval();
}
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" id="login-form" asp-action="Login">

View File

@ -1,9 +1,11 @@
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Custodians
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Models.CustodianAccountViewModels.ViewCustodianAccountViewModel
@{
ViewData.SetActivePage(AppsNavPages.Create, "Custodian account: " + @Model?.CustodianAccount.Name);
ViewData.SetActivePage(AppsNavPages.Create, "Custodian account: " + @Model?.CustodianAccount.Name);
Csp.UnsafeEval();
}
@section PageHeadContent

View File

@ -1,9 +1,11 @@
@inject BTCPayServer.Services.LanguageService langService
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model PaymentModel
@{
Layout = null;
Layout = null;
Csp.UnsafeEval();
}
<!DOCTYPE html>

View File

@ -5,12 +5,13 @@
@inject BTCPayServerEnvironment Env
@inject IEnumerable<IUIExtension> UiExtensions
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model PaymentModel
@{
Layout = null;
ViewData["Title"] = Model.HtmlTitle;
var hasPaymentPlugins = UiExtensions.Any(extension => extension.Location == "checkout-payment-method");
Layout = null;
ViewData["Title"] = Model.HtmlTitle;
Csp.UnsafeEval();
var hasPaymentPlugins = UiExtensions.Any(extension => extension.Location == "checkout-payment-method");
// Show LNURL as selectable payment method only for top up invoices + non-BIP21 case
var displayedPaymentMethods = Model.IsUnsetTopUp && !Model.OnChainWithLnInvoiceFallback
? Model.AvailableCryptos
@ -49,7 +50,7 @@
<section id="payment" v-if="isActive">
@if (!string.IsNullOrEmpty(Model.ItemDesc) && Model.ItemDesc != Model.StoreName)
{
<h5 class="text-center mt-1 mb-3 fw-semibold" v-if="srvModel.itemDesc" v-text="srvModel.itemDesc">@Model.ItemDesc</h5>
<h5 class="text-center mt-1 mb-3 fw-semibold" v-if="srvModel.itemDesc" v-text="srvModel.itemDesc"></h5>
}
@if (Model.IsUnsetTopUp)
{

View File

@ -1,8 +1,10 @@
@namespace BTCPayServer.Client
@using BTCPayServer.Abstractions.Models
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Controllers.UIManageController.ApiKeysViewModel
@{
ViewData.SetActivePage(ManageNavPages.APIKeys, "API Keys");
ViewData.SetActivePage(ManageNavPages.APIKeys, "API Keys");
Csp.UnsafeEval();
}
<div class="row">

View File

@ -3,9 +3,11 @@
@using BTCPayServer.Client
@model BTCPayServer.Models.PaymentRequestViewModels.ViewPaymentRequestViewModel
@inject BTCPayServer.Services.BTCPayServerEnvironment Env
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@{
ViewData["Title"] = Model.Title;
Layout = null;
ViewData["Title"] = Model.Title;
Csp.UnsafeEval();
Layout = null;
string StatusClass(InvoiceState state)
{
switch (state.Status.ToModernStatus())
@ -68,7 +70,7 @@
<div class="col-12 col-md-8 col-lg-9">
<div class="row">
<div class="col col-12 col-lg-8">
<h1 class="h3" v-text="srvModel.title">@Model.Title</h1>
<h1 class="h3" v-text="srvModel.title"></h1>
</div>
<div class="col col-12 col-sm-6 col-lg-8 d-flex align-items-center">
<span class="text-muted text-nowrap">Last Updated</span>
@ -204,9 +206,7 @@
<h2 class="h4 mb-3">Invoice Summary</h2>
@if (!string.IsNullOrEmpty(Model.Description) && Model.Description != "<br>")
{
<div v-html="srvModel.description">
@Safe.Raw(Model.Description)
</div>
<div v-html="srvModel.description"></div>
}
else
{

View File

@ -4,9 +4,11 @@
@using BTCPayServer.Components.ThemeSwitch
@using BTCPayServer.Payments
@model BTCPayServer.Models.ViewPullPaymentModel
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@{
ViewData["Title"] = Model.Title;
Csp.UnsafeEval();
Layout = null;
string StatusTextClass(string status)
{

View File

@ -1,7 +1,9 @@
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model WalletSetupViewModel
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Scan QR code", Context.GetStoreData().Id);
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Scan QR code", Context.GetStoreData().Id);
Csp.UnsafeEval();
}
@section Navbar {

View File

@ -2,11 +2,13 @@
@using Newtonsoft.Json
@using System.Text
@using BTCPayServer.Abstractions.Models
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model WalletSettingsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIWallets/_Nav";
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"{Model.CryptoCode} Wallet Settings", Context.GetStoreData().Id);
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIWallets/_Nav";
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"{Model.CryptoCode} Wallet Settings", Context.GetStoreData().Id);
Csp.UnsafeEval();
}
@section PageHeadContent {

View File

@ -1,11 +1,13 @@
@using BTCPayServer.Controllers
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model WalletPSBTViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
var cancelUrl = Model.ReturnUrl ?? Url.Action(nameof(UIWalletsController.WalletTransactions), new { walletId });
var backUrl = Model.BackUrl != null ? $"{Model.BackUrl}?returnUrl={Model.ReturnUrl}" : null;
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.PSBT, "Decode PSBT", walletId);
var walletId = Context.GetRouteValue("walletId").ToString();
var cancelUrl = Model.ReturnUrl ?? Url.Action(nameof(UIWalletsController.WalletTransactions), new { walletId });
var backUrl = Model.BackUrl != null ? $"{Model.BackUrl}?returnUrl={Model.ReturnUrl}" : null;
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.PSBT, "Decode PSBT", walletId);
Csp.UnsafeEval();
}
@section Navbar {

View File

@ -1,14 +1,16 @@
@using BTCPayServer.Controllers
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model WalletPSBTViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
var cancelUrl = Model.ReturnUrl ?? Url.Action(nameof(UIWalletsController.WalletTransactions), new { walletId });
var backUrl = Model.BackUrl != null ? $"{Model.BackUrl}?returnUrl={Model.ReturnUrl}" : null;
var cancelUrl = Model.ReturnUrl ?? Url.Action(nameof(UIWalletsController.WalletTransactions), new { walletId });
var backUrl = Model.BackUrl != null ? $"{Model.BackUrl}?returnUrl={Model.ReturnUrl}" : null;
var isReady = !Model.HasErrors;
var isSignable = !isReady;
var needsExport = !isSignable && !isReady;
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.PSBT, isReady ? "Confirm broadcasting this transaction" : "Transaction Details", walletId);
Csp.UnsafeEval();
}
@section PageHeadContent {

View File

@ -1,14 +1,16 @@
@inject BTCPayServer.Security.ContentSecurityPolicies csp
@using Microsoft.AspNetCore.Mvc.ModelBinding
@using BTCPayServer.Controllers
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model WalletSendModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
var cancelUrl = Model.ReturnUrl ?? Url.Action(nameof(UIWalletsController.WalletTransactions), new { walletId });
var backUrl = Model.BackUrl != null ? $"{Model.BackUrl}?returnUrl={Model.ReturnUrl}" : null;
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, $"Send {Model.CryptoCode}", walletId);
csp.Add("worker-src", "blob:");
var walletId = Context.GetRouteValue("walletId").ToString();
var cancelUrl = Model.ReturnUrl ?? Url.Action(nameof(UIWalletsController.WalletTransactions), new { walletId });
var backUrl = Model.BackUrl != null ? $"{Model.BackUrl}?returnUrl={Model.ReturnUrl}" : null;
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, $"Send {Model.CryptoCode}", walletId);
csp.Add("worker-src", "blob:");
Csp.UnsafeEval();
}
@section Navbar {