feathersjs-ecosystem / feathers-authentication-management

Adds sign up verification, forgotten password reset, and other capabilities to local feathers-authentication
https://feathers-a-m.netlify.app/
MIT License
247 stars 98 forks source link

sendResetPwd, information leak #85

Closed sylvainlap closed 6 years ago

sylvainlap commented 6 years ago

With the sendResetPwd action, if the email address does not exist, a 404 error is thrown.

I think this is an information leak, because thanks to that call, an attacker can find if an email address is valid or not (ie in the database). I think this call should not return the user info, and send the same empty response, whatever the case (user exists or not).

Regards.

jonnyparris commented 6 years ago

In case it helps anyone else, I wrote a middleware handler to intercept any 400 errors as a workaround. This way you can still benefit from the explicit error messages server side but any client side attackers/users receive a generic response:

module.exports = function(app) {
  return function(error, req, res, next) {
    let refererPath = req.headers.referer.split('/')
    refererPath = refererPath[refererPath.length-1]
    const resetPwdRequested = refererPath === 'forgot-password'
    const userNotFoundError = error && error.code === 400 && error.name === 'BadRequest' && error.message === 'User not found.'

    if (resetPwdRequested && userNotFoundError) {
      // Don't leak whether or not users exist in your DB to nefarious hackers
      // (Make sure this response is the same as when a user is found.)
      res.status(201).send({ success: true })
    }
    next(error)
  }
}
jraut commented 6 years ago

The same applies to error responses which result from unique violation for email. The instance details are revealed which contain the hashed password (well, any fields which were part of the original payload). This enables gathering a map of pwd => encrypt(pwd) and any other processing of the fields.

musicformellons commented 6 years ago

I try to find the 404 response referred above, and would expect it to show up in my chrome network tab in the status column. However I do not get any 404 (just 200) when using a not existing email. Does this mean my app is 'not vulnerable' to this kind of leaking? And why: could it be because I use websockets?

Server side I get:

error:  BadRequest: User not found.
    at new BadRequest(~/api_server/node_modules/@feathersjs/errors/lib/index.js:86:17)
    at getUserData (~/api_server/node_modules/feathers-authentication-management/lib/helpers.js:93:11)
    at ~/api_server/node_modules/feathers-authentication-management/lib/sendResetPwd.js:32:14
claustres commented 6 years ago

Well I am afraid sending 200 when there is actually an error is not really great, how do you let your users (and not attackers) know about any error ? To check the email an attacker can also try to register as it is often required to be unique and check this error, if no error is returned how do you tell your user that he has to provide another email ?

Regarding https://github.com/feathers-plus/feathers-authentication-management/issues/85#issuecomment-383924950 you probably miss a removeVerification hook to avoid leaking information.

jonnyparris commented 6 years ago

how do you let your users (and not attackers) know about any error ?

You set a generic message that your users will receive a password reset email if their details are found in your records.

To check the email an attacker can also try to register as it is often required to be unique and check this error...

True, but generally my main aim is to slow down / prevent rapid enumeration of multiple emails - something like a captcha on your registration form can help with this.

claustres commented 6 years ago

Yes or rate limiting.

eddyystop commented 6 years ago

This issue has been moved to https://github.com/feathers-plus/authentication-local-management/issues/5 and https://github.com/feathers-plus/authentication-local-management/issues/6

OnnoGabriel commented 2 years ago

This is an old and closed issue. But in case anybody searches for a solution for this issue that works with REST and Websocket transport: You can always remove the error and unify the result that is returned to the frontend in the error and after hooks of a Feathers service. Here is a simple example for the sendResetPwd action of our auth-management' service:

  app.service('/auth-management').hooks({
    error: {
      all: [
        // Delete error and return only true after "sendResetPwd" request
        // => to hide any existence or not-existence of users
        (context) => {
          if (context.data && context.data.action === 'sendResetPwd') {
            delete context.error;
            context.result = true;
          }
        },
      ],
    },
    after: {
      all: [
        // Return only true after "sendResetPwd" request
        // => to hide any existence or not-existence of users
        (context) => {
          if (context.data && context.data.action === 'sendResetPwd') {
            context.result = true;
          }
        },
      ],
    },
  });