Skip to content

Create tickets from policies vulnerabilities #341

@edimossilva

Description

@edimossilva

CRITICAL: HospitalPolicy - Missing Ownership Verification

Severity: CRITICAL

Vulnerability Type

Insecure Direct Object Reference (IDOR) - Missing Authorization Check


Summary

The HospitalPolicy allows ANY authenticated user to update or delete ANY hospital in the system. Since hospitals are global resources (not user-scoped), this vulnerability allows any user to modify or delete hospitals used by other users, potentially corrupting medical records and breaking data integrity.


Affected Files

File Line(s) Issue
app/policies/hospital_policy.rb 12-17 update? and destroy? only check user.present?
app/controllers/api/v1/hospitals_controller.rb 27, 38 Calls authorize(hospital) with weak policy
app/operations/hospitals/find.rb 10 Uses Hospital.find(id) without user scoping
app/models/hospital.rb all No user association - hospitals are global

Vulnerable Code

app/policies/hospital_policy.rb

class HospitalPolicy < ApplicationPolicy
  def index?
    user.present?
  end

  def create?
    user.present?
  end

  def update?
    user.present?  # VULNERABLE: Only checks if user is logged in
  end

  def destroy?
    user.present?  # VULNERABLE: Only checks if user is logged in
  end
end

app/controllers/api/v1/hospitals_controller.rb

def update
  authorize(hospital)  # Calls HospitalPolicy#update? which returns true for any user
  result = Hospitals::Update.result(id: hospital.id.to_s, attributes: hospital_params)
  # ...
end

def destroy
  authorize(hospital)  # Calls HospitalPolicy#destroy? which returns true for any user
  result = Hospitals::Destroy.result(id: hospital.id.to_s)
  # ...
end

private

def hospital
  @hospital ||= Hospitals::Find.result(id: params[:id]).hospital  # Fetches ANY hospital by ID
end

Exploitation Steps

Prerequisites

  • Valid authenticated API token for any user account

Attack Scenario 1: Update Any Hospital

# Step 1: List hospitals to get IDs (or enumerate from 1)
curl -X GET "https://api.example.com/api/v1/hospitals" \
  -H "Authorization: Bearer <attacker_token>"

# Response shows hospitals with IDs
# { "hospitals": [{ "id": 1, "name": "Hospital A" }, { "id": 2, "name": "Hospital B" }] }

# Step 2: Modify another user's hospital data
curl -X PATCH "https://api.example.com/api/v1/hospitals/1" \
  -H "Authorization: Bearer <attacker_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "HACKED", "address": "Modified Address"}'

# Response: 200 OK - Hospital successfully modified

Attack Scenario 2: Delete Any Hospital

# Delete a hospital used by other users
curl -X DELETE "https://api.example.com/api/v1/hospitals/1" \
  -H "Authorization: Bearer <attacker_token>"

# Response: 200 OK - Hospital deleted
# Impact: All event_procedures referencing this hospital are affected

Attack Scenario 3: ID Enumeration

# Enumerate valid hospital IDs
for id in {1..1000}; do
  response=$(curl -s -o /dev/null -w "%{http_code}" \
    -X DELETE "https://api.example.com/api/v1/hospitals/$id" \
    -H "Authorization: Bearer <attacker_token>")
  if [ "$response" == "200" ]; then
    echo "Hospital $id deleted"
  fi
done

Impact Assessment

Data Integrity

  • Event Procedures Corruption: Modifying hospital names/addresses affects all related event_procedures
  • Soft Delete Cascade: Deleting a hospital (via acts_as_paranoid) may affect related records

Business Impact

  • Medical records may become inconsistent
  • Audit trails could be compromised
  • User trust violation

Compliance Impact

  • HIPAA violations (if applicable) - unauthorized modification of medical records
  • GDPR data integrity requirements

Remediation Options

Option 1: Add User Ownership to Hospital Model (Recommended if hospitals should be user-scoped)

# Migration
class AddUserToHospitals < ActiveRecord::Migration[7.1]
  def change
    add_reference :hospitals, :user, foreign_key: true
  end
end

# Model
class Hospital < ApplicationRecord
  belongs_to :user
  # ...
end

# Policy
class HospitalPolicy < ApplicationPolicy
  def update?
    user_owner?
  end

  def destroy?
    user_owner?
  end
end

Option 2: Admin-Only Modification (If hospitals are intentionally global)

class HospitalPolicy < ApplicationPolicy
  def update?
    user.admin?
  end

  def destroy?
    user.admin?
  end
end

Option 3: Reference-Based Protection (Prevent deletion if in use)

class HospitalPolicy < ApplicationPolicy
  def destroy?
    user.admin? && record.event_procedures.empty?
  end
end

Testing Recommendations

Add the following tests to spec/policies/hospital_policy_spec.rb:

RSpec.describe HospitalPolicy do
  describe '#update?' do
    context 'when user does not own the hospital' do
      it 'returns false' do
        user = create(:user)
        hospital = create(:hospital, user: create(:another_user))

        policy = described_class.new(user, hospital)

        expect(policy.update?).to be false
      end
    end
  end

  describe '#destroy?' do
    context 'when user does not own the hospital' do
      it 'returns false' do
        user = create(:user)
        hospital = create(:hospital, user: create(:another_user))

        policy = described_class.new(user, hospital)

        expect(policy.destroy?).to be false
      end
    end
  end
end

References

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions