fix: Cross-store privilege escalation to approved pull payments/payouts in GreenfieldPullPaymentController (#7244)
What changed, and why it matters
This commit fixes a privilege escalation bug in BTCPay Server's Greenfield API. Two authorization checks were passing 'null' instead of the actual store ID when verifying whether a user could auto-approve pull payments or payouts. In BTCPay Server's authorization system, a null resource likely causes the check to fall back to a global permission evaluation, meaning a user who only had permission on one store could potentially approve or auto-approve pull payments/payouts for other stores where they should have been denied. The patch simply passes the correct storeId so the permission is scoped properly.
Upgrade to the patched version. If running an affected release, review audit logs for cross-store pull payment or payout approvals, especially auto-approved claims and manually approved payouts created by users who only had CanCreatePullPayments on a different store. Consider rotating API keys and re-evaluating store-level role assignments.
Security signals we found
Cross-store privilege escalation
Authorization bypass via null resource
Missing resource scoping in policy check
Pull payment/payout auto-approval affected
Co-authored by security researcher (Cerberus Merlin / merlin@cerberus.security)
Evidence from the diff
In GreenfieldPullPaymentController.cs, two calls to _authorizationService.AuthorizeAsync were using a null resource parameter when checking the CanCreatePullPayments policy for auto-approval features. BTCPay Server’s authorization typically evaluates store-scoped permissions by comparing the user’s store roles against the provided storeId resource. Passing null bypasses store-level scoping, allowing the authorization service to evaluate the policy globally or against any store the user belongs to. This is a classic cross-store authorization bypass: a user with CanCreatePullPayments on store A could invoke auto-approve on store B. The fix passes storeId as the resource in both call sites.
Changed components
BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.csGreenfield API pull payment creation endpointGreenfield API payout creation/update endpointAuthorization policy evaluation for CanCreatePullPaymentsInspect captured patch +2 / −2
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
index e92d3e3..9812f5f 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
@@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (request.AutoApproveClaims)
{
- if (!(await _authorizationService.AuthorizeAsync(User, null,
+ if (!(await _authorizationService.AuthorizeAsync(User, storeId,
new PolicyRequirement(Policies.CanCreatePullPayments))).Succeeded)
{
return this.CreateAPIPermissionError(Policies.CanCreatePullPayments);
@@ -479,7 +479,7 @@ retry:
{
if (request?.Approved is true)
{
- if (!(await _authorizationService.AuthorizeAsync(User, null,
+ if (!(await _authorizationService.AuthorizeAsync(User, storeId,
new PolicyRequirement(Policies.CanCreatePullPayments))).Succeeded)
{
return this.CreateAPIPermissionError(Policies.CanCreatePullPayments);
Why this scored 81/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.