Skip to content

Security: Account enumeration via 'User already exists' error message, enables attacker to enumerate valid email addresses #697

@anshul23102

Description

@anshul23102

Problem

Signup endpoint returns specific error "User already exists" (lines 19, 26 of auth.js). Attacker can enumerate valid email addresses by trying common emails and observing different responses.


Technical Details

File: backend/routes/auth.js
Lines: 19, 26

if (existingUser)
  return res.status(400).json({ message: 'User already exists' });

// Later catch block
if (err && err.code === 11000) {
  return res.status(400).json({ message: 'User already exists' });
}

Attack Scenario

Email Enumeration:

  1. Attacker tests emails: admin@company.com, john@company.com, etc.
  2. Response: 'User already exists' means email is registered
  3. Response: validation error means email format is wrong
  4. Attacker builds list of all registered emails
  5. Uses emails for targeted phishing

Recommended Solution

Return generic response for all signup failures:

router.post("/signup", validateRequest(signupSchema), async (req, res) => {
  const { username, email, password } = req.body;
  
  try {
    const existingUser = await User.findOne({
      $or: [{ email }, { username }],
    });
    
    if (existingUser) {
      // Don't reveal if email/username exists
      return res.status(400).json({
        message: 'Signup failed. Please try again or contact support.'
      });
    }
    
    const newUser = new User({ username, email, password });
    await newUser.save();
    
    // Also generic success message
    res.status(201).json({
      message: 'Account created. Please log in.',
      // Don't return user object
    });
  } catch (err) {
    // All errors return same message
    res.status(400).json({
      message: 'Signup failed. Please try again or contact support.'
    });
  }
});

Rate limiting to prevent brute force:

const rateLimit = require('express-rate-limit');

const signupLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 5,  // 5 requests per window
  message: 'Too many signup attempts. Try again later.'
});

router.post("/signup", signupLimiter, validateRequest(signupSchema), async (req, res) => {
  // ...
});

Testing Strategy

  • Test: 'User already exists' message removed
  • Test: All errors return generic message
  • Test: Enumeration attempts fail
  • Rate limit: 5 signup attempts per 15 minutes
  • Audit: No information leakage in error responses

Program Template

  • GSSoC '26

Suggested Labels

security, enumeration, authentication, gssoc-eligible

Metadata

Metadata

Assignees

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