diff --git a/api.js b/api.js index 39b35e4..8c925c4 100644 --- a/api.js +++ b/api.js @@ -11,11 +11,9 @@ const Users = database.Users; const Secrets = database.Secrets; const safeCompare = require('safe-compare'); const { Op } = require("sequelize"); -const Settings = database.Settings; const PayloadFireResults = database.PayloadFireResults; const CollectedPages = database.CollectedPages; const InjectionRequests = database.InjectionRequests; -const update_settings_value = database.update_settings_value; const constants = require('./constants.js'); const validate = require('express-jsonschema').validate; const get_hashed_password = require('./utils.js').get_hashed_password; @@ -54,11 +52,7 @@ function session_wrapper_function(req, res, next) { async function set_up_api_server(app) { // Check for existing session secret value - const session_secret_setting = await Settings.findOne({ - where: { - key: constants.session_secret_key - } - }); + const session_secret_setting = process.env.SESSION_SECRET_KEY; if (!session_secret_setting) { console.error(`No session secret is set, can't start API server (this really shouldn't happen...)!`); @@ -118,6 +112,10 @@ async function set_up_api_server(app) { constants.API_BASE_PATH + 'payloadfires', constants.API_BASE_PATH + 'collected_pages', constants.API_BASE_PATH + 'settings', + constants.API_BASE_PATH + 'xss-uri', + constants.API_BASE_PATH + 'user-path', + constants.API_BASE_PATH + '', + ]; // Check if the path being accessed required authentication @@ -486,13 +484,13 @@ async function set_up_api_server(app) { } } app.post(constants.API_BASE_PATH + 'record_injection', validate({ body: RecordCorrelatedRequestSchema }), async (req, res) => { - const correlation_key_record = await Settings.findOne({ + const user = await Users.findOne({ where: { - key: constants.CORRELATION_API_SECRET_SETTINGS_KEY + injectionCorrelationAPIKey: req.body.owner_correlation_key } }); - if (!safeCompare(correlation_key_record.value, req.body.owner_correlation_key)) { + if (! user) { res.status(200).json({ "success": false, "error": "Invalid authentication provided. Please provide a proper correlation API key.", @@ -535,53 +533,18 @@ async function set_up_api_server(app) { Returns current settings values for the UI */ app.get(constants.API_BASE_PATH + 'settings', async (req, res) => { - const settings_to_retrieve = [ - { - key: constants.CORRELATION_API_SECRET_SETTINGS_KEY, - return_key: 'correlation_api_key', - default: '', - formatter: false, - }, - { - key: constants.CHAINLOAD_URI_SETTINGS_KEY, - return_key: 'chainload_uri', - default: '', - formatter: false, - }, - { - key: constants.PAGES_TO_COLLECT_SETTINGS_KEY, - return_key: 'pages_to_collect', - default: [], - formatter: ((value) => { - return JSON.parse(value); - }), - }, - { - key: constants.SEND_ALERT_EMAILS_KEY, - return_key: 'send_alert_emails', - default: true, - formatter: ((value) => { - return JSON.parse(value); - }), - }, - ]; - - let result = {}; - let database_promises = settings_to_retrieve.map(async settings_value_metadata => { - const db_record = await Settings.findOne({ - where: { - key: settings_value_metadata.key - } - }); - - const formatter_function = settings_value_metadata.formatter ? settings_value_metadata.formatter : (value) => value; - result[settings_value_metadata.return_key] = db_record ? formatter_function(db_record.value) : settings_value_metadata.default; - }); - await Promise.all(database_promises); - + let returnObj = {} + const user = await Users.findOne({ where: { 'id': req.session.user_id } }); + if(! user){ + return res.send("Invalid"); + } + returnObj.correlation_api_key = user.injectionCorrelationAPIKey; + returnObj.chainload_uri = user.additionalJS; + returnObj.send_alert_emails = user.sendEmailAlerts; + res.status(200).json({ 'success': true, - result + returnObj }).end(); }); @@ -621,60 +584,25 @@ async function set_up_api_server(app) { } } app.put(constants.API_BASE_PATH + 'settings', validate({ body: UpdateConfigSchema }), async (req, res) => { - + const user = await Users.findOne({ where: { 'id': req.session.user_id } }); + if(! user){ + return res.send("Invalid"); + } if(req.body.correlation_api_key === true) { - const correlation_api_key = get_secure_random_string(64); - await update_settings_value( - constants.CORRELATION_API_SECRET_SETTINGS_KEY, - correlation_api_key - ); + user.injectionCorrelationAPIKey = req.body.correlation_api_key; } // Intentionally no URL validation incase people want to do // data: for inline extra JS. if(req.body.chainload_uri) { - await update_settings_value( - constants.CHAINLOAD_URI_SETTINGS_KEY, - req.body.chainload_uri - ); + user.additionalJS = req.body.chainload_uri; } if(req.body.send_alert_emails !== undefined) { - await update_settings_value( - constants.SEND_ALERT_EMAILS_KEY, - req.body.send_alert_emails.toString() - ); + user.sendEmailAlerts = req.body.send_alert_emails; } - // Immediately rotate session secret and revoke all sessions. - if(req.body.revoke_all_sessions !== undefined) { - const new_session_secret = get_secure_random_string(64); - // Update session secret in database - const session_secret_setting = await Settings.findOne({ - where: { - key: constants.session_secret_key - } - }); - session_secret_setting.value = new_session_secret; - await session_secret_setting.save(); - - // We do this by patching the sessions middleware at runtime - // to utilize a new HMAC secret so all previous sessions are revoked. - const updated_session_settings = { - ...sessions_settings_object, - ...{ - secret: session_secret_setting.value - } - }; - sessions_middleware = sessions(updated_session_settings); - } - - if(req.body.pages_to_collect) { - await update_settings_value( - constants.PAGES_TO_COLLECT_SETTINGS_KEY, - JSON.stringify(req.body.pages_to_collect) - ); - } + await user.save(); res.status(200).json({ 'success': true, diff --git a/app.js b/app.js index b6ad568..be4c317 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,6 @@ const path = require('path'); const asyncfs = require('fs').promises; const uuid = require('uuid'); const database = require('./database.js'); -const Settings = database.Settings; const PayloadFireResults = database.PayloadFireResults; const savePayload = database.savePayload; const Users = database.Users; @@ -333,28 +332,17 @@ async function get_app_server() { } console.log(`Got xss fetch for user ${user.email}`); - const db_promises = [ - Settings.findOne({ - where: { - key: constants.PAGES_TO_COLLECT_SETTINGS_KEY, - } - }), - Settings.findOne({ - where: { - key: constants.CHAINLOAD_URI_SETTINGS_KEY, - } - }), - ]; - const db_results = await Promise.all(db_promises); - const pages_to_collect = (db_results[0] === null) ? [] : JSON.parse(db_results[0].value); - const chainload_uri = (db_results[1] === null) ? '' : db_results[1].value; + const chainload_uri = user.additionalJS; + if (! chainload_uri){ + chainload_uri = ''; + } res.send(XSS_PAYLOAD.replace( /\[HOST_URL\]/g, `https://${process.env.XSS_HOSTNAME}` ).replace( '[COLLECT_PAGE_LIST_REPLACE_ME]', - JSON.stringify(pages_to_collect) + JSON.stringify([]) ).replace( /\[USER_PATH\]/g, userPath diff --git a/database.js b/database.js index e69ad8e..bedd8bb 100644 --- a/database.js +++ b/database.js @@ -19,46 +19,6 @@ const sequelize = new Sequelize( const Model = Sequelize.Model; -/* - Storage for XSS Hunter Express settings. - - All settings keys must be unique. - - Additionally stores admin credentials for the - single user that can authenticate. -*/ -class Settings extends Model {} -Settings.init({ - id: { - allowNull: false, - primaryKey: true, - type: Sequelize.UUID, - defaultValue: uuid.v4() - }, - // Setting name - key: { - type: Sequelize.TEXT, - allowNull: true, - unique: true - }, - // Setting value - value: { - type: Sequelize.TEXT, - allowNull: true, - }, -}, { - sequelize, - modelName: 'settings', - indexes: [ - { - unique: true, - fields: ['key'], - method: 'BTREE', - } - ] -}); - - /* Secrets found in DOMs */ @@ -76,10 +36,22 @@ Users.init({ unique: true }, path: { - type: Sequelize.TEXT, - allowNull: true, + type: sequelize.text, + allownull: true, + unique: true + }, + injectionCorrelationAPIKey: { + type: sequelize.text, + allownull: true, + unique: true + }, + additionalJS: { + type: sequelize.text, + allownull: true, unique: true } + + }, { sequelize, modelName: 'users', @@ -381,57 +353,6 @@ InjectionRequests.init({ ] }); -async function initialize_configs() { - // Check for existing session secret value - const session_secret_setting = await Settings.findOne({ - where: { - key: constants.session_secret_key - } - }); - - // If it exists, there's nothing else to do here. - if(session_secret_setting) { - return - } - - console.log(`No session secret set, generating one now...`); - - // Since it doesn't exist, generate one. - await Settings.create({ - id: uuid.v4(), - key: constants.session_secret_key, - value: get_secure_random_string(64) - }); - - console.log(`Session secret generated successfully!`); -} - -async function setup_admin_user(password) { - // If there's an existing admin user, skip this. - // Check for existing session secret value - const admin_user_password = await Settings.findOne({ - where: { - key: constants.ADMIN_PASSWORD_SETTINGS_KEY - } - }); - - // If user is already set up then there's nothing - // for us to do here, return. - if(admin_user_password) { - return false - } - - const bcrypt_hash = await get_hashed_password(password); - - // Set up the admin user - await Settings.create({ - id: uuid.v4(), - key: constants.ADMIN_PASSWORD_SETTINGS_KEY, - value: bcrypt_hash - }); - - return true; -} function get_default_user_created_banner(password) { return ` @@ -444,16 +365,7 @@ function get_default_user_created_banner(password) { ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - An admin user (for the admin control panel) has been created - with the following password: - - PASSWORD: ${password} - - XSS Hunter Express has only one user for the instance. Do not - share this password with anyone who you don't trust. Save it - in your password manager and don't change it to anything that - is bruteforcable. - + Hi. I love you. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ █████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ ██████╗ ███╗ ██╗ ██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║ @@ -466,60 +378,16 @@ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv `; } -async function initialize_users() { - // Check if the admin user has been created. - // If not then set it up. - - // Generate cryptographically-secure random - // password for the default user we're adding. - const new_password = get_secure_random_string(32); - - // Create user and add to database - const new_user_created = await setup_admin_user( - new_password - ); - - if(!new_user_created) { - return - } - - // Now we need to write these credentials to the - // filesystem in a file so the user can retrieve - // them. - const banner_message = get_default_user_created_banner( - new_password - ); - +async function print_banner() { console.log(banner_message); } -// Set up correlation API with a randomly -// generated API key to auth with. -async function initialize_correlation_api() { - const existing_correlation_key = await Settings.findOne({ - where: { - key: constants.CORRELATION_API_SECRET_SETTINGS_KEY - } - }); - - if(existing_correlation_key) { - return - } - - const api_key = get_secure_random_string(64); - await Settings.create({ - id: uuid.v4(), - key: constants.CORRELATION_API_SECRET_SETTINGS_KEY, - value: api_key - }); -} async function database_init() { const force = false; // Set up database schema await Promise.all([ - Settings.sync({ force: force }), PayloadFireResults.sync({ force: force }), Users.sync({ force: force }), Secrets.sync({ force: force }), @@ -528,45 +396,17 @@ async function database_init() { ]); await Promise.all([ - // Set up configs if they're not already set up. - initialize_configs(), - // Set up admin panel user if not already set up. - initialize_users(), - - // Set up the correlation API if not already set up - initialize_correlation_api(), + print_banner(), ]); } -async function update_settings_value(settings_key, new_value) { - const settings_record = await Settings.findOne({ - where: { - key: settings_key - } - }); - - if(settings_record) { - settings_record.value = new_value; - await settings_record.save(); - return - } - - await Settings.create({ - id: uuid.v4(), - key: settings_key, - value: new_value - }); -} - module.exports = { sequelize, - Settings, PayloadFireResults, CollectedPages, InjectionRequests, database_init, - update_settings_value, savePayload, Secrets, Users diff --git a/docker-compose.yml b/docker-compose.yml index 3a941c9..f00f47c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,8 @@ services: # CLIENT ID FOR OAUTH LOGIN - CLIENT_ID=your_client_id - CLIENT_SECRET=your_client_secret + # GENERATE A RANDOM LONG STRING FOR THIS + - SESSION_SECRET_KEY= # THERE IS NO NEED TO MODIFY BELOW THIS LINE # ------------------------------------------ # FEEL FREE, BUT KNOW WHAT YOU'RE DOING. diff --git a/front-end/src/App.vue b/front-end/src/App.vue index 714d3ae..1b0ef56 100644 --- a/front-end/src/App.vue +++ b/front-end/src/App.vue @@ -7,13 +7,6 @@

XSS Hunter
Please login to continue.

- - - Authenticate - - - Incorrect password, try again. -
diff --git a/front-end/src/pages/Settings.vue b/front-end/src/pages/Settings.vue index e4e7c42..6512909 100644 --- a/front-end/src/pages/Settings.vue +++ b/front-end/src/pages/Settings.vue @@ -5,7 +5,7 @@
-

Settings

+

User Settings

Injection Correlation API Key

@@ -40,42 +40,8 @@ Save JavaScript URL
- -

Pages to Collect on Payload Fire

-
List of relative paths to collect from a site when a payload fires.
-
-
- -
-
- - Add New Path to Collect - -
-
-

- - - -

- - Delete Selected Path(s) - -
- - Update Pages to Collect List - -

Miscellaneous Options

- - Revoke All Active Sessions - -
Invalidates all active login sessions. This will log you out as well.
-
-
Disable Email Reporting