mirror of
https://github.com/trufflesecurity/xsshunter
synced 2024-11-24 05:13:04 +00:00
removing a lot
This commit is contained in:
parent
e9854f3032
commit
eccf511385
6 changed files with 51 additions and 334 deletions
124
api.js
124
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,
|
||||
|
|
22
app.js
22
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
|
||||
|
|
194
database.js
194
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -7,13 +7,6 @@
|
|||
<card type="secondary" header-classes="bg-white pb-5" body-classes="px-lg-5 py-lg-5" class="border-0 mb-0" style="text-align: center">
|
||||
<h3>XSS Hunter<br />
|
||||
<i>Please login to continue.</i></h3>
|
||||
<base-input alternative v-model="password" type="password" placeholder="Password" autofocus v-on:keyup.enter="attempt_login"></base-input>
|
||||
<base-button block simple type="primary" v-on:click="attempt_login">
|
||||
<i class="fas fa-key"></i> Authenticate
|
||||
</base-button>
|
||||
<base-alert v-if="invalid_password_used" class="mt-4" type="danger">
|
||||
<i class="fas fa-times"></i> Incorrect password, try again.
|
||||
</base-alert>
|
||||
</card>
|
||||
</modal>
|
||||
<div class="loading-bar" v-if="loading">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<card class="xss-card-container">
|
||||
<div class="row pl-4 pr-4 p-2" style="display: block;">
|
||||
<div>
|
||||
<h1><i class="fas fa-cogs"></i> Settings</h1>
|
||||
<h1><i class="fas fa-cogs"></i>User Settings</h1>
|
||||
</div>
|
||||
<card>
|
||||
<h4 class="card-title">Injection Correlation API Key</h4>
|
||||
|
@ -40,42 +40,8 @@
|
|||
<i class="far fa-save"></i> Save JavaScript URL
|
||||
</base-button>
|
||||
</card>
|
||||
<card>
|
||||
<h4 class="card-title">Pages to Collect on Payload Fire</h4>
|
||||
<h6 class="card-subtitle mb-2 text-muted">List of relative paths to collect from a site when a payload fires.</h6>
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<base-input class="mt-1" type="text" placeholder="/robots.txt" v-model="new_page_to_collect" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<base-button type="primary" v-on:click="add_new_page_to_collect">
|
||||
<i class="fas fa-plus"></i> Add New Path to Collect
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text">
|
||||
<base-input >
|
||||
<select multiple class="form-control" v-model="selected_page_to_collect">
|
||||
<option v-for="page_to_collect in pages_to_collect">{{page_to_collect}}</option>
|
||||
</select>
|
||||
</base-input>
|
||||
</p>
|
||||
<base-button type="danger" class="btn-sm mt-0 mb-3" v-on:click="delete_selected_pages_to_collect">
|
||||
<i class="fas fa-trash-alt"></i> Delete Selected Path(s)
|
||||
</base-button>
|
||||
<br />
|
||||
<base-button type="primary" v-on:click="update_pages_to_collect">
|
||||
<i class="far fa-save"></i> Update Pages to Collect List
|
||||
</base-button>
|
||||
</card>
|
||||
<card>
|
||||
<h4 class="card-title">Miscellaneous Options</h4>
|
||||
<base-button type="danger" v-on:click="revoke_all_sessions">
|
||||
<i class="fas fa-user-times"></i> Revoke All Active Sessions
|
||||
</base-button>
|
||||
<h6 class="mt-2 text-muted">Invalidates all active login sessions. This will log you out as well.</h6>
|
||||
<hr />
|
||||
|
||||
<div v-if="send_alert_emails">
|
||||
<base-button type="primary" v-on:click="set_email_reporting">
|
||||
<i class="far fa-bell-slash"></i> Disable Email Reporting
|
||||
|
|
Loading…
Reference in a new issue