Send emails with sendgrid (#14)

* Use sendgrid to send emails

* add unsubscribe list

* fix test code

* update enabled var and readme

* email template

---------

Co-authored-by: counter <counter@counters-MacBook-Air.local>
This commit is contained in:
Dustin Decker 2023-01-29 11:46:28 -08:00 committed by GitHub
parent 38bebeda34
commit b1b48104e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 3236 additions and 60 deletions

View file

@ -21,14 +21,10 @@ The following are some YAML fields (in [`docker-compose.yaml`](https://github.co
The following are needed if you want email notifications:
* `SMTP_EMAIL_NOTIFICATIONS_ENABLED`: Leave enabled to receive email notifications (you must set this up via the below configurations as well).
* `SMTP_HOST`: The host of your SMTP server where your email account is hosted (e.g. `smtp.gmail.com`).
* `SMTP_PORT`: The port of your SMTP server (e.g. `465`).
* `SMTP_USE_TLS`: Utilize TLS if your SMTP server supports it.
* `SMTP_USERNAME`: The username of the email account on your SMTP server (e.g. `exampleuser`).
* `SMTP_PASSWORD`: The password of the email account on your SMTP server (e.g. `Password1!`).
* `SMTP_FROM_EMAIL`: The email address of your email account on the SMTP server (e.g. `exampleuser@gmail.com`).
* `SMTP_RECEIVER_EMAIL`: What email the notifications will be sent to. This may be the same as the above but could be different.
* `EMAIL_NOTIFICATIONS_ENABLED`: Leave enabled to receive email notifications (you must set this up via the below configurations as well).
* `SENDGRID_API_KEY`: API key for Sendgrid
* `SENDGRID_UNSUBSRIBE_GROUP_ID`: Unsubscribe group ID for emails
Finally, the following is worth considering for the security conscious:

3
app.js
View file

@ -300,8 +300,9 @@ async function get_app_server() {
console.log("saved record");
// Send out notification via configured notification channel
if(user.sendEmailAlerts && process.env.SMTP_EMAIL_NOTIFICATIONS_ENABLED=="true") {
if(user.sendEmailAlerts && process.env.EMAIL_NOTIFICATIONS_ENABLED=="true") {
payload_fire_data.screenshot_url = `https://${process.env.HOSTNAME}/screenshots/${payload_fire_data.screenshot_id}.png`;
payload_fire_data.xsshunter_url = `https://${process.env.HOSTNAME}`;
await notification.send_email_notification(payload_fire_data, user.email);
}
});

View file

@ -1,4 +1,5 @@
const nodemailer = require('nodemailer');
const sendgrid = require('@sendgrid/mail')
sendgrid.setApiKey(process.env.SENDGRID_API_KEY)
const mustache = require('mustache');
const fs = require('fs');
@ -8,30 +9,32 @@ const XSS_PAYLOAD_FIRE_EMAIL_TEMPLATE = fs.readFileSync(
);
async function send_email_notification(xss_payload_fire_data, email) {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT),
secure: (process.env.SMTP_USE_TLS === "true"),
auth: {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD,
},
});
const notification_html_email_body = mustache.render(
XSS_PAYLOAD_FIRE_EMAIL_TEMPLATE,
xss_payload_fire_data
);
const info = await transporter.sendMail({
from: process.env.SMTP_FROM_EMAIL,
const msg = {
from: process.env.EMAIL_FROM,
to: email,
subject: `[XSS Hunter Express] XSS Payload Fired On ${xss_payload_fire_data.url}`,
text: "Only HTML reports are available, please use an email client which supports this.",
html: notification_html_email_body,
});
asm: {
groupId: parseInt(process.env.SENDGRID_UNSUBSRIBE_GROUP_ID),
groupsToDisplay: [
parseInt(process.env.SENDGRID_UNSUBSRIBE_GROUP_ID)
]
},
}
response = await sendgrid
.send(msg)
.catch((error) => {
console.error(error);
})
console.log("Message sent: %s", info.messageId);
console.log("Message emailed with status %d", response[0].statusCode);
return true;
}
module.exports.send_email_notification = send_email_notification;

24
notification.test.js Normal file
View file

@ -0,0 +1,24 @@
const notification = require('./notification');
test('send an email notification', async () => {
const email = "dustin@trufflesec.com"
var payload_fire_data = {
id: 1,
user_id: 2,
url: "http://google.com",
ip_address: "127.0.0.1",
referer: "http://google.com",
user_agent: "TruffleHog",
cookies: [],
title: "Hello",
secrets: {},
origin: "http://google.com",
screenshot_id: 1,
was_iframe: true,
browser_timestamp: 1,
correlated_request: 'No correlated request found for this injection.',
}
await expect(notification.send_email_notification(payload_fire_data, email)).resolves.toBe(true);
});

3186
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,22 +4,24 @@
"description": "Simplified and easy-to-setup version of XSS Hunter for self-hosting.",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "jest",
"start": "node server.js"
},
"author": "mandatory (Matthew Bryant)",
"license": "MIT",
"dependencies": {
"@deveodk/vue-toastr": "^1.1.0",
"@google-cloud/storage": "^6.9.0",
"@sendgrid/mail": "^7.7.0",
"@truffledustin/node-client-sessions": "^0.8.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.20.1",
"cors": "^2.8.5",
"@google-cloud/storage": "^6.9.0",
"express": "^4.17.1",
"express-jsonschema": "^1.1.6",
"google-auth-library": "^8.7.0",
"googleapis": "^110.0.0",
"jest": "^29.4.1",
"keygrip": "^1.1.0",
"memorystore": "^1.6.6",
"moment": "^2.29.1",

View file

@ -8,8 +8,8 @@
</head>
<body style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin: 0;font-family: &quot;Helvetica Neue&quot;,Helvetica,Arial,sans-serif;font-size: 14px;line-height: 1.42857143;color: #333;background-color: #fff;">
<h1 style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin: .67em 0;font-size: 36px;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 20px;margin-bottom: 10px;">XSS Hunter Express Report</h1>
This report has been generated by an XSS Hunter Express server and contains the details of a cross-site scripting vulnerability. The tracking ID for this report is <code style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;font-family: Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;font-size: 90%;padding: 2px 4px;color: #c7254e;background-color: #f9f2f4;border-radius: 4px;">{{ id }}</code>, the triggering browser reports the time of execution to be {{ browser_timestamp }}.
<h1 style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin: .67em 0;font-size: 36px;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 20px;margin-bottom: 10px;">XSSHunter Report</h1>
This report has been generated by an XSSHunter server and contains the details of a cross-site scripting vulnerability. To view more details including vulnerability checks for secrets, CORS, and .git exposed, login here: <a href="{{xsshunter_url}}" the triggering browser reports the time of execution to be {{ browser_timestamp }}.
<hr style="-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;height: 0;margin-top: 20px;margin-bottom: 20px;border: 0;border-top: 1px solid #eee;">
<div class="panel panel-default" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin-bottom: 20px;background-color: #fff;border: 1px solid transparent;border-radius: 4px;-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);box-shadow: 0 1px 2px rgba(0,0,0,.05);border-color: #ddd;">
<div class="panel-heading" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px 15px;border-bottom: 1px solid transparent;border-top-left-radius: 3px;border-top-right-radius: 3px;color: #333;background-color: #f5f5f5;border-color: #ddd;background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat: repeat-x;">
@ -43,14 +43,6 @@
<code style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;font-family: Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;font-size: 90%;padding: 2px 4px;color: #c7254e;background-color: #f9f2f4;border-radius: 4px;">{{ user_agent }}</code>
</div>
</div>
<div class="panel panel-default" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin-bottom: 20px;background-color: #fff;border: 1px solid transparent;border-radius: 4px;-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);box-shadow: 0 1px 2px rgba(0,0,0,.05);border-color: #ddd;">
<div class="panel-heading" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px 15px;border-bottom: 1px solid transparent;border-top-left-radius: 3px;border-top-right-radius: 3px;color: #333;background-color: #f5f5f5;border-color: #ddd;background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat: repeat-x;">
<h3 class="panel-title" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;orphans: 3;widows: 3;page-break-after: avoid;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 0;margin-bottom: 0;font-size: 16px;">Cookies</h3>
</div>
<div class="panel-body" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 15px;">
<code style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;font-family: Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;font-size: 90%;padding: 2px 4px;color: #c7254e;background-color: #f9f2f4;border-radius: 4px;">{{ cookies }}</code>
</div>
</div>
<div class="panel panel-default" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin-bottom: 20px;background-color: #fff;border: 1px solid transparent;border-radius: 4px;-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);box-shadow: 0 1px 2px rgba(0,0,0,.05);border-color: #ddd;">
<div class="panel-heading" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px 15px;border-bottom: 1px solid transparent;border-top-left-radius: 3px;border-top-right-radius: 3px;color: #333;background-color: #f5f5f5;border-color: #ddd;background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat: repeat-x;">
<h3 class="panel-title" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;orphans: 3;widows: 3;page-break-after: avoid;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 0;margin-bottom: 0;font-size: 16px;">Injection Point (Raw HTTP Request)</h3>
@ -59,22 +51,6 @@
<pre class="pre-scrollable" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;overflow: auto;display: block;padding: 9.5px;margin: 0 0 10px;font-size: 1em;line-height: 1.42857143;color: #333;word-break: break-all;word-wrap: break-word;background-color: #f5f5f5;border: 1px solid #999;border-radius: 4px;font-family: Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;page-break-inside: avoid;max-height: 340px;overflow-y: scroll;">{{correlated_request}}</pre>
</div>
</div>
<div class="panel panel-default" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin-bottom: 20px;background-color: #fff;border: 1px solid transparent;border-radius: 4px;-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);box-shadow: 0 1px 2px rgba(0,0,0,.05);border-color: #ddd;">
<div class="panel-heading" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px 15px;border-bottom: 1px solid transparent;border-top-left-radius: 3px;border-top-right-radius: 3px;color: #333;background-color: #f5f5f5;border-color: #ddd;background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat: repeat-x;">
<h3 class="panel-title" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;orphans: 3;widows: 3;page-break-after: avoid;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 0;margin-bottom: 0;font-size: 16px;">Page DOM</h3>
</div>
<div class="panel-body" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 15px;">
<pre class="pre-scrollable" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;overflow: auto;display: block;padding: 9.5px;margin: 0 0 10px;font-size: 1em;line-height: 1.42857143;color: #333;word-break: break-all;word-wrap: break-word;background-color: #f5f5f5;border: 1px solid #999;border-radius: 4px;font-family: Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;page-break-inside: avoid;max-height: 340px;overflow-y: scroll;">{{ dom }}</pre>
</div>
</div>
<div class="panel panel-default" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin-bottom: 20px;background-color: #fff;border: 1px solid transparent;border-radius: 4px;-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);box-shadow: 0 1px 2px rgba(0,0,0,.05);border-color: #ddd;">
<div class="panel-heading" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px 15px;border-bottom: 1px solid transparent;border-top-left-radius: 3px;border-top-right-radius: 3px;color: #333;background-color: #f5f5f5;border-color: #ddd;background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat: repeat-x;">
<h3 class="panel-title" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;orphans: 3;widows: 3;page-break-after: avoid;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 0;margin-bottom: 0;font-size: 16px;">Page Text</h3>
</div>
<div class="panel-body" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 15px;">
<pre class="pre-scrollable" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;overflow: auto;display: block;padding: 9.5px;margin: 0 0 10px;font-size: 1em;line-height: 1.42857143;color: #333;word-break: break-all;word-wrap: break-word;background-color: #f5f5f5;border: 1px solid #999;border-radius: 4px;font-family: Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;page-break-inside: avoid;max-height: 340px;overflow-y: scroll;">{{ text }}</pre>
</div>
</div>
<div class="panel panel-default" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;margin-bottom: 20px;background-color: #fff;border: 1px solid transparent;border-radius: 4px;-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);box-shadow: 0 1px 2px rgba(0,0,0,.05);border-color: #ddd;">
<div class="panel-heading" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;padding: 10px 15px;border-bottom: 1px solid transparent;border-top-left-radius: 3px;border-top-right-radius: 3px;color: #333;background-color: #f5f5f5;border-color: #ddd;background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat: repeat-x;">
<h3 class="panel-title" style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;orphans: 3;widows: 3;page-break-after: avoid;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 0;margin-bottom: 0;font-size: 16px;">Execution Origin</h3>
@ -86,6 +62,8 @@
<h3 style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;orphans: 3;widows: 3;page-break-after: avoid;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;margin-top: 20px;margin-bottom: 10px;font-size: 24px;"><i style="-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;">A screenshot of the affected page has been included for further investigation.</i></h3>
<hr style="-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;height: 0;margin-top: 20px;margin-bottom: 20px;border: 0;border-top: 1px solid #eee;">
<img alt="Enable images to see the XSS screenshot" src="{{ screenshot_url }}" />
<br>
</body>
</html>
</html>