Skip to content

Commit 49f1422

Browse files
jasnowRubySec CI
authored andcommitted
Updated advisory posts against rubysec/ruby-advisory-db@281e70f
1 parent 4eb715a commit 49f1422

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
layout: advisory
3+
title: 'GHSA-mjgf-xj26-9qf9 (pay): pay-rails/pay - non-constant-time HMAC comparison
4+
in Paddle Billing webhook signature verifier'
5+
comments: false
6+
categories:
7+
- pay
8+
advisory:
9+
gem: pay
10+
ghsa: mjgf-xj26-9qf9
11+
url: https://github.com/advisories/GHSA-mjgf-xj26-9qf9
12+
title: pay-rails/pay - non-constant-time HMAC comparison in Paddle Billing webhook
13+
signature verifier
14+
date: 2026-07-01
15+
description: |-
16+
## Summary
17+
18+
The `Pay::Webhooks::PaddleBillingController#valid_signature?`
19+
(`app/controllers/pay/webhooks/paddle_billing_controller.rb`)
20+
verifies the Paddle Billing webhook signature by computing
21+
`OpenSSL::HMAC.hexdigest(...)` and comparing it to the attacker-supplied
22+
header value using Ruby's `String#==`. Ruby's `==` is non-constant-time —
23+
it returns as soon as the first byte mismatches — and exposes a per-byte
24+
timing side channel on the webhook signature verification path. The
25+
canonical mitigation is to use a constant-time primitive
26+
(`OpenSSL.fixed_length_secure_compare` /
27+
`ActiveSupport::SecurityUtils.secure_compare`).
28+
29+
- IMPACT - CWE-208 — Observable Timing Discrepancy on the webhook
30+
signature verifier.
31+
- An attacker who can deliver requests to the `/pay/webhooks/paddle_billing`
32+
mount point can probe the verifier with guessed `Paddle-Signature`
33+
header values. Because `String#==` short-circuits on the first
34+
mismatching byte, the response-time distribution shifts as the prefix
35+
of the guess matches the real hex digest.
36+
- A signature recovered through the oracle lets the attacker deliver
37+
forged Paddle Billing webhook events (e.g. `subscription.created` /
38+
`transaction.completed`) against the host application. Pay's webhook
39+
'processor enqueues a `Pay::Webhooks::ProcessJob` for any accepted'
40+
webhook, which downstream applications use to update billing state —
41+
including provisioning paid features, recording refunds, and
42+
triggering customer notifications.
43+
- The endpoint is internet-reachable by definition (Paddle must
44+
POST events to it).
45+
46+
- Credit - Reported by tonghuaroot (https://github.com/tonghuaroot)
47+
cvss_v3: 7.4
48+
related:
49+
url:
50+
- https://advisories.gitlab.com/gem/pay/GHSA-mjgf-xj26-9qf9
51+
- https://github.com/pay-rails/pay/security/advisories/GHSA-mjgf-xj26-9qf9
52+
- https://github.com/advisories/GHSA-mjgf-xj26-9qf9
53+
notes: |
54+
- Latest (unpatched) release as of 7/2/2026 is 11.6.1
55+
- `pay` (rubygem) ≤ v11.6.1 (latest release as of 2026-05-27).
56+
- https://github.com/pay-rails/pay/releases/tag/v11.6.1
57+
- https://rubygems.org/gems/pay/versions/11.6.1
58+
- cvss_v3 value comes from project advisory.
59+
- No cve value, so missing cvss_v2 and cvss_v4 values.
60+
---

0 commit comments

Comments
 (0)