Skip to content

bug: upload-avatar endpoint trusts JWT payload without verifying signature #90

@FuturMix

Description

@FuturMix

Summary

The POST /api/auth/upload-avatar and DELETE /api/auth/upload-avatar endpoints decode the JWT payload using atob() without validating the cryptographic signature. This allows any client to craft a JWT with an arbitrary sub claim and upload/delete avatars for other users.

Location

src/app/api/auth/upload-avatar/route.js — lines 13-42 (POST) and lines 130-159 (DELETE)

Root Cause

The code manually splits the JWT into parts and decodes the payload:

const tokenParts = token.split('.');
const payload = JSON.parse(atob(tokenParts[1]));
user = { id: payload.sub, email: payload.email };

It checks token format (3 parts) and expiration, but never validates the signature (the third part). An attacker can set sub to any user ID.

The endpoint then uses a service role client (SUPABASE_SERVICE_ROLE_KEY) to update the database, bypassing RLS entirely.

Impact

  • An attacker can upload a malicious avatar for any user
  • An attacker can delete any user's avatar (via DELETE)
  • Storage pollution under arbitrary user ID paths

Reproduction

// Craft a JWT with arbitrary sub — no valid signature needed
const fakePayload = btoa(JSON.stringify({
  sub: 'TARGET_USER_AUTH_ID',
  email: 'attacker@example.com',
  exp: Math.floor(Date.now() / 1000) + 3600
}));
const fakeToken = `eyJhbGciOiJIUzI1NiJ9.${fakePayload}.fakesignature`;

// Server accepts this token and updates TARGET_USER's avatar
fetch('/api/auth/upload-avatar', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${fakeToken}` },
  body: formDataWithImage
});

Fix

Replace the manual JWT decoding with supabase.auth.getUser() which validates the signature via Supabase's auth infrastructure. Other endpoints in this codebase (e.g., /api/crypto/public-keys) already use this pattern correctly.

Comparison

The correct pattern exists at /api/crypto/public-keys/route.js:

const { data: { user }, error } = await supabase.auth.getUser(accessToken);

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