...
 
Commits (3)
import {
frontendUrl,
mailSenderDomain,
} from '../config';
export const from = `"Email Privacy Tester" <noreply@${mailSenderDomain}>`;
export const from = '"Email Privacy Tester" <noreply@%%MAIL_SENDER_DOMAIN%%>';
export const subject = 'EPT - Confirm Your Email';
export const list = {
unsubscribe: {
url: `${frontendUrl}/optout?code=%%CODE%%`,
comment: `Opt out from further emails from ${frontendUrl}`,
url: '%%FRONTEND_URL%%/optout?code=%%CODE%%',
comment: 'Opt out from further emails from %%FRONTEND_URL%%',
},
};
export const text = `Hi there,
This email was trigger by somebody at IP address %%IP%% (hopefully you) entering the email address %%EMAIL%% into the form at ${frontendUrl}. If this was not you, please don't hit the spam button in your email client as it may make it more difficult for me to send email to others who have legitimately requested it. Instead, visit the optout link at the end of this email and you will never receive any email from this service again.
This email was trigger by somebody at IP address %%IP%% (hopefully you) entering the email address %%EMAIL%% into the form at %%FRONTEND_URL%%. If this was not you, please don't hit the spam button in your email client as it may make it more difficult for me to send email to others who have legitimately requested it. Instead, visit the optout link at the end of this email and you will never receive any email from this service again.
If it *was* you, please visit the following link so you can initiate tests and view their results:
Confirm: ${frontendUrl}/test?code=%%CODE%%
Confirm: %%FRONTEND_URL%%/test?code=%%CODE%%
If you find this service useful, and have the means to do so, please consider making a small donation:
Donate: ${frontendUrl}/donate
Donate: %%FRONTEND_URL%%/donate
If you run an email service, you may find another free project I created useful:
......@@ -36,7 +31,7 @@ Here is my tech blog:
And finally, here is the optout link:
Opt out: ${frontendUrl}/optout?code=%%CODE%%
Opt out: %%FRONTEND_URL%%/optout?code=%%CODE%%
Regards,
......
import React from 'react';
import {
backendUrl,
frontendDomain,
frontendUrl,
mailSenderDomain,
} from '../config';
export const from = `"Email Privacy Tester" <noreply@${mailSenderDomain}>`;
export const subject = `EPT - Your Test Email - ${frontendUrl}/confirm?code=%%CODE%%`;
import svg from './svg';
import svg2 from './svg2';
/**
* Helper function to hex encode a string. Like the standard escape function
* but does it to *every* character
*/
function hexEncode(str) {
let ret = '';
let c = str.length;
while (c) {
let hexChar = str.charCodeAt(--c).toString(16);
if (hexChar.length < 2) hexChar = '0' + hexChar;
ret = '%' + hexChar + ret;
}
return ret;
}
const jsm = 'XSS(1) found. Please report to xss@emailprivacytester.com';
const js1 = `alert(unescape(/${hexEncode(jsm)}/.source))`;
const js2 = `<script>alert(unescape(/${hexEncode(jsm.replace('(1)','(2)'))}/.source))</script>`;
const js3 = `alert(unescape(/${hexEncode(jsm.replace('(1)','(3)'))}/.source))`;
export const from = `"Email Privacy Tester <img src=x onerror='${js3}'>" <noreply@%%MAIL_SENDER_DOMAIN%%>`;
export const subject = 'EPT - Your Test Email - %%FRONTEND_URL%%/test?code=%%CODE%%';
export const list = {
unsubscribe: {
url: `${frontendUrl}/optout?code=%%CODE%%`,
comment: `Opt out from further emails from ${frontendUrl}`,
url: '%%FRONTEND_URL%%/optout?code=%%CODE%%',
comment: 'Opt out from further emails from %%FRONTEND_URL%%',
},
};
export const html = ({ code }) => {
export const attachments = [
{
filename: '%%CODE%%.css',
content: '* { background-image: url(\'%%BASE%%css_attachment\'); }',
cid: '%%CODE%%.css@%%FRONTEND_DOMAIN%%',
contentDisposition: 'inline',
contentType: 'text/css',
},
{
filename: '%%CODE%%.svg',
cid: '%%CODE%%.svg@%%FRONTEND_DOMAIN%%',
contentDisposition: 'inline',
contentType: 'image/svg+xml',
content: svg,
},
{
filename: '%%CODE%%-2.svg',
cid: '%%CODE%%.svg2@%%FRONTEND_DOMAIN%%',
contentDisposition: 'inline',
contentType: 'image/svg+xml',
content: svg2,
},
{
filename: `${js2}.%%CODE%%.png`,
contents: js1,
cid: '%%CODE%%.js@%%FRONTEND_DOMAIN%%',
contentDisposition: 'inline',
contentType: 'image/png',
},
];
const base = `${backendUrl}/callback?code=${code}&test=`;
export const html = ({ code, base, frontendDomain }) => {
return (
<html lang="en-GB" manifest={ `${base}manifest` }>
......
const svg = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" width="60" height="60" version="1.1" baseProfile="full"
onload="init(evt)">
<script type="text/javascript">
function init( evt ){
try {
alert('Wow. JavaScript hidden inside SVG\'s executes in your email client. That\'s a potentially serious security proble
m. Please contact me using the form at https://grepular.com/contact about this.');
} catch(e){}
}
</script>
<text x="0" y="0" style="background-image:url('%%BASE%%svg_background_img')"/>
</svg>`;
export default svg;
import svg from './svg';
export default svg
.replace(/(<!DOCTYPE [^>]+)/, '$1 [ <!ELEMENT svg ANY')
.replace(/(<svg )/, '\n<!ENTITY xxe SYSTEM "%%BASE%%svg_xxe" >]>\n$1')
.replace(/(<text[^>]+)\/>/, '$1>&xxe;</text>');
......@@ -3,19 +3,17 @@ import { renderToStaticMarkup } from 'react-dom/server';
import nodeMailer from 'nodemailer';
import smtpTransport from 'nodemailer-smtp-transport';
import Optouts from './db/model/Optouts';
import Optouts from '../db/model/Optouts';
import {
mailServerHost,
mailServerPort,
mailSenderDomain,
} from '../config';
import * as config from '../../config';
const { mailServerHost = 'localhost', mailServerPort = 25 } = config;
const transporter = nodeMailer.createTransport(smtpTransport({
host: mailServerHost || 'localhost',
port: mailServerPort || 25,
ignoreTLS: (!mailServerHost || mailServerHost === 'localhost'),
secure: !(!mailServerHost || mailServerHost === 'localhost'),
host: mailServerHost,
port: mailServerPort,
ignoreTLS: mailServerHost === 'localhost',
secure: mailServerHost !== 'localhost',
}));
function replaceTemplateParams (msg, params) {
......@@ -40,7 +38,8 @@ function replaceTemplateParams (msg, params) {
if (typeof msg === 'string') {
return Object.keys(params).reduce((txt, param) => {
const rx = new RegExp(`%%${param.toUpperCase()}%%`, 'g');
const k = param.split(/([A-Z])/).map(s => s.replace(/^([A-Z])$/, `_${s}`)).join('');
const rx = new RegExp(`%%${k.toUpperCase()}%%`, 'g');
return txt.replace(rx, params[param]);
}, msg);
}
......@@ -54,7 +53,7 @@ export default {
async send (options, params = {}) {
const msg = replaceTemplateParams(options, {
mail_sender_domain: mailSenderDomain,
...config,
...params,
});
......
......@@ -20,11 +20,12 @@ async function rateLimit(ip, email, age, limit) {
},
{
$match: {
time: { $gt: new Date(Date.now() - age) },
$or: [
{ ip },
{ email: email.toLowerCase().trim() },
{ time: { $gt: new Date(Date.now() - age) } },
],
time: { $gt: new Date(Date.now() - age) },
},
},
])
......
......@@ -2,6 +2,7 @@ import Email from '../lib/Email';
import Tests from '../lib/db/model/Tests';
import { backendUrl } from '../config';
import * as testEmail from '../emails/test';
export default async function sendTestRoute (req, res) {
......@@ -23,7 +24,10 @@ export default async function sendTestRoute (req, res) {
const response = await Email.send({
...testEmail,
to: email,
}, { code });
}, {
code,
base: `${backendUrl}/callback?code=${code}&test=`,
});
await Tests.update({ _id: code }, {
accessed: true,
......
......@@ -9,6 +9,7 @@ import Wrapper from '../lib/components/Wrapper';
const style = {
error: css({ maxWidth: '30em', color: 'red' }),
instructions: css({ maxWidth: '30em' }),
form: css({ marginTop: '1em' }),
};
export default class HomePage extends Component {
......@@ -38,14 +39,16 @@ export default class HomePage extends Component {
return (
<Wrapper title="Email Privacy Tester">
<p className={ style.instructions }>
You might want to check what this is for if this is your first time
here. Check out the <Link href="/about">about page</Link>, and if
you're interested, the <Link href="/privacy">privacy page</Link>
If you fill in the field below we will send you a confirmation email
with further instructions. If this is your first time here, you might
want to read the <Link href="/about">about page</Link> and/or
the <Link href="/privacy">privacy policy</Link>.
</p>
<form
disabled = { submitting }
ref = { el => this._form = el }
onSubmit = { this.onSubmit }
className = { style.form }
disabled = { submitting }
ref = { el => this._form = el }
onSubmit = { this.onSubmit }
>
<input
type = "email"
......@@ -57,18 +60,17 @@ export default class HomePage extends Component {
autoFocus = { true }
/>
<button disabled = { submitting }>
Submit
{ submitting ? 'Sending' : 'Submit' }
</button>
</form>
{
success && (
<p className={ style.instructions }>
<strong>Email successfully sent to { success.email }</strong>.
Once it has arrived, click the confirm link, and you will be
taken to a page where you can trigger test emails to be sent
to your email address.
</p>
<p className={ style.instructions }>
<strong>Email successfully sent to { success.email }</strong>.
Once it has arrived, click the confirm link, and you will be
taken to a page where you can trigger test emails to be sent
to your email address.
</p>
)
}
{
......
......@@ -27,3 +27,11 @@ p {
height: 100%;
width: 100%;
}
input[type="email"],
button {
font-size: 1em;
line-height: 1.5;
font-family: 'Open Sans', sans-serif;
padding: 0.25em 1em;
}