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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions