HexBrain

Basecamp · Report #3543475

Improper Access Control in `fizzy.do` import flow allows cross-tenant ActionText reference resolution and data disclosure

Validstatus
lowseverity
53upvotes
14 Apr 2026disclosed

How it was found

Description

The account import flow processes ActionText attachment HTML from user-uploaded ZIP content.

In app/models/account/data_transfer/action_text_rich_text_record_set.rb, import-time method convert_gids_to_sgids converts attacker-controlled gid values into persisted sgid values by resolving the target record globally:

  • app/models/account/data_transfer/action_text_rich_text_record_set.rb:83
  • app/models/account/data_transfer/action_text_rich_text_record_set.rb:87
  • app/models/account/data_transfer/action_text_rich_text_record_set.rb:88
  • app/models/account/data_transfer/action_text_rich_text_record_set.rb:89

The import path has no tenant ownership check before minting the signed reference.

Reachable import path from normal upload flow:

  • app/controllers/account/imports_controller.rb:38
  • app/models/account/import.rb:37
  • app/models/account/import.rb:39

For comparison, export path has an account check (record&.account_id == account.id) in the same file at app/models/account/data_transfer/action_text_rich_text_record_set.rb:69.

Reproduction Path A (Fastest for triage, deterministic)

This path is self-contained and exercises the real vulnerable import code path (import_batch) without requiring full web flow setup.

Prerequisites

  • Ruby and Bundler installed
  • Repo cloned

Steps

  1. Enter repo:
cd /path/to/fizzy
  1. Install/check dependencies:
bundle check || bundle install
  1. Run standalone integration PoC:
bundle exec ruby security-poc/integration_test_standalone.rb
  1. Expected success signals in output:
  • RESULT: IMPACT_CONFIRMED=true
  • imported.account_id = <attacker_account_id>
  • resolved_account_id = <victim_account_id> (different tenant)
  • attachable_text = @alice_secret

Why this is valid

This script loads and executes the real vulnerable file:

  • app/models/account/data_transfer/action_text_rich_text_record_set.rb

And runs:

  • Account::DataTransfer::ActionTextRichTextRecordSet#import_batch

So the exploit condition is validated directly against production logic.

Reproduction Path B (Full product-equivalent import flow)

This path uses the Rails environment and import conversion path end-to-end.

Steps

  1. Enter repo:
cd /path/to/fizzy
  1. Install/check dependencies:
bundle check || bundle install
  1. Run full import impact demo:
DISABLE_BOOTSNAP=1 RAILS_ENV=development \
bundle exec rails runner security-poc/demo_import_cross_account_impact.rb
  1. Expected success signals:
  • cross_account_reference=true
  • IMPACT_CONFIRMED=true
  • imported_rich_text_account_id=<attacker_account_id>
  • resolved_account_id=<victim_account_id>
  • leaked_attachable_text=@alice

Note

In slower environments, Rails boot may take a few minutes before script output appears.

Evidence from local execution

Observed in local runs:

  • Path A (integration_test_standalone.rb): RESULT: IMPACT_CONFIRMED=true
  • Path B (demo_import_cross_account_impact.rb): IMPACT_CONFIRMED=true

Both runs showed attacker-owned imported rich text resolving a victim-owned attachable record.

Supporting files

  • security-poc/integration_test_standalone.rb
  • security-poc/demo_import_cross_account_impact.rb
  • security-poc/patch_action_text_gid.py

Suggested remediation

In convert_gids_to_sgids, only mint sgid when resolved record belongs to importing account:

  • Resolve record safely (handle not found)
  • Require record.respond_to?(:account_id) && record.account_id == account.id
  • Drop or ignore cross-account references

Impact

Impact

  • Cross-tenant unauthorized data reference in imported rich text.
  • Victim-account attachable data is resolved/rendered in attacker-account context.
  • Persisted unauthorized reference (sgid) remains stored until removed.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original