Summary
A cross-organization IDOR vulnerability exists in SubscriptionViewSet.get_object() that allows authenticated users to access, modify, or cancel subscriptions belonging to other organizations.
Vulnerable Code
File: backend/api/views.py, line 775
class SubscriptionViewSet(PermissionPolicyMixin, ...):
queryset = SubscriptionRecord.base_objects.all() # Unscoped
def get_object(self):
subscription_uuid = SubscriptionUUIDField().to_internal_value(...)
obj = self.queryset.get(subscription_record_id=subscription_uuid) # Uses unscoped queryset
return obj
def get_queryset(self): # Correctly scoped
qs = SubscriptionRecord.base_objects.all()
organization = self.request.organization
qs = qs.filter(organization=organization) # Proper org filter
return qs
Root Cause
`self.queryset` returns the unscoped class-level queryset. `self.get_queryset()` applies the organization filter. Line 775 uses the wrong one.
All 16 other ViewSets in the codebase correctly use `self.get_queryset()` or `super().get_object()` - this is a 1-of-17 inconsistency.
Affected Endpoints
All endpoints using get_object():
- GET /api/subscriptions/{id}/ (read other org's subscription)
- POST /api/subscriptions/{id}/cancel (cancel other org's subscription)
- POST /api/subscriptions/{id}/switch_plan (change plan)
- POST /api/subscriptions/{id}/attach_addon, /update_addon, /change_prepaid_units
Fix
Change line 775 from:
obj = self.queryset.get(subscription_record_id=subscription_uuid)
To:
obj = self.get_queryset().get(subscription_record_id=subscription_uuid)
Summary
A cross-organization IDOR vulnerability exists in
SubscriptionViewSet.get_object()that allows authenticated users to access, modify, or cancel subscriptions belonging to other organizations.Vulnerable Code
File:
backend/api/views.py, line 775Root Cause
`self.queryset` returns the unscoped class-level queryset. `self.get_queryset()` applies the organization filter. Line 775 uses the wrong one.
All 16 other ViewSets in the codebase correctly use `self.get_queryset()` or `super().get_object()` - this is a 1-of-17 inconsistency.
Affected Endpoints
All endpoints using get_object():
Fix
Change line 775 from:
To: