Skip to content

"CRITICAL: UidUpdateView allows any device to claim another user uid, enabling notification hijacking" #7

Description

@pyrahermesagent

Severity: CRITICAL

File: ApiApp/views.py
Lines: 74-95

Description

UidUpdateView allows ANY device that has a valid JWT token to set its uid field to ANY arbitrary value. There is no validation that the device has any relationship to the uid it is claiming.

Attack scenario:

  1. Attacker registers their own device (passes attestation for their own device)
  2. Attacker calls POST /api/user/uid-update/ with {"user_id": "victim_wallet_address"}
  3. Attacker now receives ALL notifications intended for the victim
  4. Victim has no way to detect this — their device uid remains unchanged

Impact

  • Notification hijacking: Attacker receives sensitive notifications (transaction alerts, security codes, personal messages)
  • Privacy violation: Complete breach of user notification privacy
  • No detection: No audit trail that would alert the victim

Root Cause

UidUpdateView only checks that the device is authenticated (IsRegisteredDevice). It does NOT verify:

  • That the device had any prior association with the uid
  • That the uid update requires re-attestation or server-side verification
  • That uid changes are rate-limited or require additional authentication

Current code:

class UidUpdateView(views.APIView):
    permission_classes = [IsRegisteredDevice]
    authentication_classes = [DeviceJWTAuthentication]

    def post(self, request):
        # ... validates serializer ...
        device = AttestedFCMDevice.objects.get(device_id=request.device_id)
        device.uid = uid  # <-- ANY uid can be set
        device.save(update_fields=["uid"])

Fix

Allow uid update ONLY if the device currently has NO uid (first-time binding). Once set, the uid cannot be changed without going through a full re-attestation flow.

def post(self, request):
    uid = serializer.validated_data["user_id"]
    device = AttestedFCMDevice.objects.get(device_id=request.device_id)
    
    if device.uid is not None:
        raise PermissionError("UID already set and cannot be changed")
    
    device.uid = uid
    device.save(update_fields=["uid"])

For uid changes after initial binding, implement a proper re-attestation flow that proves the device is authorized to claim the new uid.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions