Skip to content

Security: Cross-organization IDOR in 5 controllers (Requests, Donations, Kits, Announcements, Distributions) #5509

@lighthousekeeper1212

Description

@lighthousekeeper1212

Summary

Multiple controllers use Model.find(params[:id]) without scoping to current_organization, allowing authenticated users from one organization to read and modify data belonging to other organizations.

Affected Controllers

1. RequestsController (state-changing)

  • show (line 31): Request.find(params[:id]) — cross-org read
  • start (line 45): Request.find(params[:id]) + request.status_started! — cross-org state change

Compare with index (line 6) which properly uses current_organization.ordered_requests.

2. DonationsController (data modification)

  • print (line 6): Donation.find(params[:id]) — cross-org PDF generation
  • show (line 66): Donation.find(params[:id]) — cross-org read
  • edit (line 56): Donation.find(params[:id]) — cross-org read
  • update (line 71): Donation.find(params[:id]) — cross-org modification

Compare with destroy (line 90) which properly uses current_organization.id.

3. KitsController (inventory manipulation)

  • deactivate (line 47): Kit.find(params[:id]) — cross-org state change
  • reactivate (line 53): Kit.find(params[:id]) — cross-org state change
  • allocations (line 63): Kit.find(params[:id]) — cross-org read
  • allocate (line 71): Kit.find(params[:id]) — cross-org inventory modification

Compare with index (line 7) which properly uses current_organization.kits.

4. BroadcastAnnouncementsController

  • set_broadcast_announcement (line 48): BroadcastAnnouncement.find(params[:id]) — affects edit/update/destroy

Compare with index (line 5) which properly filters by organization_id: current_organization.id.

5. DistributionsController

  • print (line 17): Distribution.find(params[:id]) — cross-org PDF with partner data

Root Cause

The authorize_user method in ApplicationController checks that the user has a valid role for some organization, but doesn't enforce that the accessed record belongs to the user's organization. Controllers that use Model.find(params[:id]) instead of current_organization.model_name.find(params[:id]) bypass organization isolation.

Fix

Replace Model.find(params[:id]) with current_organization.model_name.find(params[:id]) in all affected actions:

# Before (vulnerable)
@request = Request.find(params[:id])

# After (fixed)
@request = current_organization.requests.find(params[:id])

This pattern is already correctly used in many other controllers and some actions within the affected controllers themselves.


Found via automated security audit. CWE-639: Authorization Bypass Through User-Controlled Key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions