Skip to content

Security: Profile IDOR + mass viewing status deletion + profileId header spoofing #1164

@lighthousekeeper1212

Description

@lighthousekeeper1212

Summary

Multiple authorization bypass vulnerabilities in profile and viewing status management. Core issue: profile operations lack user ownership checks, and the profileId request header is trusted without validation.

Finding 1: Profile IDOR - View/Update/Delete Any User's Profile

File: grails-app/controllers/streama/ProfileController.groovy

show() (line 18), update() (line 43), and delete() (line 62) accept a profile ID but have ZERO ownership checks. Any authenticated user can view, modify, or delete any other user's profile.

// Line 18 - NO ownership check
def show(Profile profile) {
    respond profile  // Returns any profile by ID
}

// Line 43 - NO ownership check  
def update(Profile profile) {
    profile.save()  // Saves modifications to any profile
    respond profile, [status: OK]
}

// Line 62 - NO ownership check
def delete(Profile profile) {
    profile.isDeleted = true
    profile.save()  // Deletes any profile
}

Secure sibling in the SAME file - getUserProfiles() (line 77) correctly filters:

def getUserProfiles() {
  def result = Profile.where {
    user == springSecurityService.getCurrentUser()  // CORRECT
    isDeleted != true
  }.list()
}

Classic 1-of-N inconsistency: 1 method checks ownership, 4 methods don't.

Finding 2: Mass ViewingStatus Deletion

File: grails-app/controllers/streama/DashController.groovy (lines 233-238)

def markAsCompleted(Video video){
  ViewingStatus.where{
    video == video  // NO user filter!
  }.deleteAll()  // Deletes viewing status for ALL users
}

Secure sibling - VideoController.markCompleted() correctly gets current user's status:

def markCompleted(Video videoInstance){
  ViewingStatus viewingStatus = videoInstance.getViewingStatus()  // Current user only
  viewingStatus.completed = true
  viewingStatus.save()
}

Any authenticated user can wipe the viewing progress of ALL users for any video.

Finding 3: profileId Header Spoofing

Multiple controllers trust the profileId request header without verifying the profile belongs to the current user:

  • WatchlistEntryController (lines 16-47, 50-90, 92-117): create/delete/list watchlist entries for any profile
  • ViewingStatusController (lines 23-41): create viewing status for any profile
  • PlayerController (lines 26-37): update viewing status for any profile
  • DashController (lines 13-18): list continue-watching for any profile

Pattern in all affected controllers:

Long profileId = request.getHeader('profileId')?.toLong()
Profile currentProfile = Profile.findById(profileId)  // No ownership check!
// ... uses currentProfile without verifying it belongs to currentUser

Impact

  • Any user can view/modify/delete other users' profiles (including child/parental control profiles)
  • Any user can wipe viewing progress for ALL users on any video
  • Any user can read/modify watchlists and viewing history of any profile
  • Undermines parental controls and privacy between household members

Suggested Fixes

  1. Add profile.user == springSecurityService.currentUser check to show/update/delete
  2. Add user filter to markAsCompleted(): user == springSecurityService.currentUser
  3. Validate profileId header ownership in a shared interceptor

CWE-639 (Authorization Bypass Through User-Controlled Key), CWE-862 (Missing Authorization)

Metadata

Metadata

Assignees

No one assigned

    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