removing a lot

This commit is contained in:
counter 2023-01-16 20:35:16 -08:00
parent e9854f3032
commit eccf511385
6 changed files with 51 additions and 334 deletions

120
api.js
View file

@ -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
let returnObj = {}
const user = await Users.findOne({ where: { 'id': req.session.user_id } });
if(! user){
return res.send("Invalid");
}
});
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);
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,

20
app.js
View file

@ -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,
const chainload_uri = user.additionalJS;
if (! chainload_uri){
chainload_uri = '';
}
}),
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;
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

View file

@ -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

View file

@ -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.

View file

@ -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">

View file

@ -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