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

Disclosed: 2026-04-14 19:25:39 By xavlimsg To basecamp
Low
Vulnerability Details
## 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: ```bash cd /path/to/fizzy ``` 2. Install/check dependencies: ```bash bundle check || bundle install ``` 3. Run standalone integration PoC: ```bash bundle exec ruby security-poc/integration_test_standalone.rb ``` 4. 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: ```bash cd /path/to/fizzy ``` 2. Install/check dependencies: ```bash bundle check || bundle install ``` 3. Run full import impact demo: ```bash DISABLE_BOOTSNAP=1 RAILS_ENV=development \ bundle exec rails runner security-poc/demo_import_cross_account_impact.rb ``` 4. 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.
Actions
View on HackerOne
Report Stats
  • Report ID: 3543475
  • State: Closed
  • Substate: resolved
  • Upvotes: 4
Share this report