2021-05-31 19:06:40 +00:00
|
|
|
const Sequelize = require('sequelize');
|
|
|
|
const uuid = require('uuid');
|
|
|
|
|
|
|
|
const get_secure_random_string = require('./utils.js').get_secure_random_string;
|
|
|
|
const get_hashed_password = require('./utils.js').get_hashed_password;
|
|
|
|
const constants = require('./constants.js');
|
|
|
|
|
|
|
|
const sequelize = new Sequelize(
|
2023-01-29 20:40:54 +00:00
|
|
|
process.env.POSTGRES_DB,
|
|
|
|
process.env.POSTGRES_USER,
|
|
|
|
process.env.POSTGRES_PASSWORD,
|
2021-05-31 19:06:40 +00:00
|
|
|
{
|
|
|
|
host: process.env.DATABASE_HOST,
|
|
|
|
dialect: 'postgres',
|
|
|
|
benchmark: true,
|
2023-01-25 16:54:53 +00:00
|
|
|
logging: false,
|
|
|
|
dialectOptions: {
|
2023-01-27 00:35:10 +00:00
|
|
|
socketPath: process.env.NODE_ENV == 'production' ? process.env.DATABASE_HOST : null,
|
2023-01-25 16:54:53 +00:00
|
|
|
},
|
2021-05-31 19:06:40 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const Model = Sequelize.Model;
|
|
|
|
|
2023-01-16 01:52:10 +00:00
|
|
|
/*
|
|
|
|
Secrets found in DOMs
|
|
|
|
*/
|
|
|
|
class Users extends Model {}
|
2023-01-16 01:54:26 +00:00
|
|
|
Users.init({
|
2023-01-16 01:52:10 +00:00
|
|
|
id: {
|
|
|
|
allowNull: false,
|
|
|
|
primaryKey: true,
|
|
|
|
type: Sequelize.UUID,
|
2023-01-30 18:49:17 +00:00
|
|
|
defaultValue: Sequelize.UUIDV4
|
2023-01-16 01:52:10 +00:00
|
|
|
},
|
|
|
|
email: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: false,
|
|
|
|
unique: true
|
|
|
|
},
|
2023-02-21 20:05:01 +00:00
|
|
|
pgp_key: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allownull: true,
|
|
|
|
unique: false
|
|
|
|
},
|
2023-01-16 01:52:10 +00:00
|
|
|
path: {
|
2023-01-17 05:07:43 +00:00
|
|
|
type: Sequelize.TEXT,
|
2023-01-17 04:35:16 +00:00
|
|
|
allownull: true,
|
|
|
|
unique: true
|
|
|
|
},
|
|
|
|
injectionCorrelationAPIKey: {
|
2023-01-17 05:07:43 +00:00
|
|
|
type: Sequelize.TEXT,
|
2023-01-17 04:35:16 +00:00
|
|
|
allownull: true,
|
|
|
|
unique: true
|
|
|
|
},
|
|
|
|
additionalJS: {
|
2023-01-17 05:07:43 +00:00
|
|
|
type: Sequelize.TEXT,
|
2023-01-17 04:35:16 +00:00
|
|
|
allownull: true,
|
2023-01-17 05:04:53 +00:00
|
|
|
},
|
|
|
|
sendEmailAlerts: {
|
2023-01-17 05:07:43 +00:00
|
|
|
type: Sequelize.BOOLEAN,
|
2023-01-17 05:04:53 +00:00
|
|
|
allownull: false,
|
2023-01-17 05:07:43 +00:00
|
|
|
defaultValue: true,
|
2023-01-16 01:52:10 +00:00
|
|
|
}
|
2023-01-17 04:35:16 +00:00
|
|
|
|
|
|
|
|
2023-01-16 01:52:10 +00:00
|
|
|
}, {
|
|
|
|
sequelize,
|
2023-01-16 01:54:26 +00:00
|
|
|
modelName: 'users',
|
2023-01-16 01:52:10 +00:00
|
|
|
indexes: [
|
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['email'],
|
|
|
|
method: 'BTREE',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['path'],
|
|
|
|
method: 'BTREE',
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2023-01-14 05:05:33 +00:00
|
|
|
/*
|
|
|
|
Secrets found in DOMs
|
|
|
|
*/
|
|
|
|
class Secrets extends Model {}
|
|
|
|
Secrets.init({
|
|
|
|
id: {
|
|
|
|
allowNull: false,
|
|
|
|
primaryKey: true,
|
|
|
|
type: Sequelize.UUID,
|
2023-01-30 18:49:17 +00:00
|
|
|
defaultValue: Sequelize.UUIDV4
|
2023-01-14 05:05:33 +00:00
|
|
|
},
|
|
|
|
payload_id: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: false,
|
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
secret_type: {
|
2023-01-14 05:18:40 +00:00
|
|
|
type: Sequelize.TEXT,
|
2023-01-14 05:05:33 +00:00
|
|
|
allowNull: false,
|
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
secret_value: {
|
2023-01-14 05:18:40 +00:00
|
|
|
type: Sequelize.TEXT,
|
2023-01-14 05:05:33 +00:00
|
|
|
allowNull: true,
|
|
|
|
unique: false
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
sequelize,
|
|
|
|
modelName: 'secrets',
|
|
|
|
indexes: [
|
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['secret_type'],
|
|
|
|
method: 'BTREE',
|
2023-01-30 23:02:29 +00:00
|
|
|
}
|
2023-01-14 05:05:33 +00:00
|
|
|
]
|
|
|
|
});
|
|
|
|
|
2021-05-31 19:06:40 +00:00
|
|
|
/*
|
|
|
|
XSS payload fire results
|
|
|
|
*/
|
|
|
|
class PayloadFireResults extends Model {}
|
|
|
|
PayloadFireResults.init({
|
|
|
|
id: {
|
|
|
|
allowNull: false,
|
|
|
|
primaryKey: true,
|
|
|
|
type: Sequelize.UUID,
|
2023-01-30 18:49:17 +00:00
|
|
|
defaultValue: Sequelize.UUIDV4
|
2021-05-31 19:06:40 +00:00
|
|
|
},
|
2023-02-21 20:05:01 +00:00
|
|
|
//boolean if it's encrypted or not
|
|
|
|
encrypted: {
|
|
|
|
type: Sequelize.BOOLEAN,
|
|
|
|
allowNull: false,
|
|
|
|
unique: false,
|
|
|
|
defaultValue: false
|
|
|
|
},
|
|
|
|
//the encrypted data blob
|
|
|
|
encrypted_data: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
//the public key used to encrypt the data
|
|
|
|
public_key: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
unique: false
|
|
|
|
},
|
2021-05-31 19:06:40 +00:00
|
|
|
// URL the XSS payload fired on.
|
|
|
|
url: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
2023-01-16 03:12:21 +00:00
|
|
|
// The id of the user who the payload goes with
|
|
|
|
user_id: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: false,
|
|
|
|
unique: false
|
|
|
|
},
|
2021-05-31 19:06:40 +00:00
|
|
|
// IP address of the user that
|
|
|
|
// triggered the XSS payload fire.
|
|
|
|
ip_address: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// The referer for the page the
|
|
|
|
// XSS payload fired on.
|
|
|
|
referer: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// User-Agent of the browser for
|
|
|
|
// the user who triggered the XSS
|
|
|
|
// payload fire.
|
|
|
|
user_agent: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// Cookies of the user for the domain
|
|
|
|
// the payload fired on.
|
|
|
|
// Obviously, this excludes HTTPOnly
|
|
|
|
cookies: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// Title of the page which the payload fired on.
|
|
|
|
title: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// HTTP origin of the page (e.g.
|
|
|
|
// https://example.com)
|
|
|
|
origin: {
|
|
|
|
type: Sequelize.TEXT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// Random ID of the screenshot
|
|
|
|
screenshot_id: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// Whether the payload fired inside
|
|
|
|
// of an iframe or not.
|
|
|
|
was_iframe: {
|
|
|
|
type: Sequelize.BOOLEAN,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// Timestamp as reported by the
|
|
|
|
// user's browser
|
|
|
|
browser_timestamp: {
|
|
|
|
type: Sequelize.BIGINT,
|
2023-02-21 20:05:01 +00:00
|
|
|
allowNull: true,
|
2021-05-31 19:06:40 +00:00
|
|
|
unique: false
|
|
|
|
},
|
2023-01-29 00:47:46 +00:00
|
|
|
// git directory exposed
|
|
|
|
gitExposed: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
unique: false
|
|
|
|
},
|
|
|
|
// cors data
|
|
|
|
CORS: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
unique: false
|
|
|
|
},
|
2021-05-31 19:06:40 +00:00
|
|
|
}, {
|
|
|
|
sequelize,
|
|
|
|
modelName: 'payload_fire_results',
|
|
|
|
indexes: [
|
2023-01-16 03:12:21 +00:00
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['user_id'],
|
|
|
|
method: 'BTREE',
|
|
|
|
},
|
2021-05-31 19:06:40 +00:00
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['ip_address'],
|
|
|
|
method: 'BTREE',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['origin'],
|
|
|
|
method: 'BTREE',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['was_iframe'],
|
|
|
|
method: 'BTREE',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
unique: false,
|
|
|
|
fields: ['browser_timestamp'],
|
|
|
|
method: 'BTREE',
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
2023-01-14 05:17:01 +00:00
|
|
|
let savePayload = async function(inbound_payload){
|
|
|
|
let payload = await PayloadFireResults.create(inbound_payload);
|
2023-02-21 20:05:01 +00:00
|
|
|
if(inbound_payload.secrets){
|
|
|
|
for (const secret of inbound_payload.secrets){
|
|
|
|
secret.payload_id = payload.id;
|
|
|
|
await Secrets.create(secret);
|
|
|
|
}
|
2023-01-14 05:05:33 +00:00
|
|
|
}
|
|
|
|
return payload
|
|
|
|
}
|
|
|
|
|
2021-05-31 19:06:40 +00:00
|
|
|
class CollectedPages extends Model {}
|
|
|
|
CollectedPages.init({
|
|
|
|
id: {
|
|
|
|
allowNull: false,
|
|
|
|
primaryKey: true,
|
|
|
|
type: Sequelize.UUID,
|
2023-01-30 18:49:17 +00:00
|
|
|
defaultValue: Sequelize.UUIDV4
|
2021-05-31 19:06:40 +00:00
|
|
|
},
|
|
|
|
// URL of the collected page
|
|
|
|
uri: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: false,
|
|
|
|
},
|
|
|
|
// HTML response of page
|
|
|
|
html: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: true,
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
sequelize,
|
|
|
|
modelName: 'collected_pages',
|
2023-01-30 23:02:29 +00:00
|
|
|
indexes: []
|
2021-05-31 19:06:40 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
class InjectionRequests extends Model {}
|
|
|
|
InjectionRequests.init({
|
|
|
|
id: {
|
|
|
|
allowNull: false,
|
|
|
|
primaryKey: true,
|
|
|
|
type: Sequelize.UUID,
|
2023-01-30 18:49:17 +00:00
|
|
|
defaultValue: Sequelize.UUIDV4
|
2021-05-31 19:06:40 +00:00
|
|
|
},
|
|
|
|
/*
|
|
|
|
The full text of the request for a given
|
|
|
|
injection attempt. For example, if you're
|
|
|
|
doing an HTTP request with an XSS payload
|
|
|
|
in a header, you'd include the full request.
|
|
|
|
|
|
|
|
This isn't necessarily exclusive to HTTP. Any
|
|
|
|
protocol can be used so long as it's converted
|
|
|
|
to text and the information is sent to the API.
|
|
|
|
|
|
|
|
(Must be text just so it can be displayed in
|
|
|
|
the final XSS payload fire report).
|
|
|
|
*/
|
|
|
|
request: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: false,
|
|
|
|
},
|
|
|
|
/*
|
|
|
|
Each injection attempt has a unique shortkey
|
|
|
|
which is used to correlate the attempt with
|
|
|
|
the resulting XSS payload fire. By using this
|
|
|
|
functionality you can always know what request
|
|
|
|
you did which resulted in a given XSS payload fire.
|
|
|
|
|
|
|
|
These unique shortkeys look like the following:
|
|
|
|
|
|
|
|
<script src="https://xss.express/mwnba6k2"></script>
|
|
|
|
|
|
|
|
In the above payload, "mwnba6k2" is the shortkey used
|
|
|
|
to track injection attempts. In practice, users will
|
|
|
|
utilize a tool which automatically generates these IDs
|
|
|
|
and sends the injeciton request details to the API.
|
|
|
|
*/
|
|
|
|
injection_key: {
|
|
|
|
type: Sequelize.TEXT,
|
|
|
|
allowNull: false,
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
sequelize,
|
|
|
|
modelName: 'injection_requests',
|
|
|
|
indexes: [
|
|
|
|
{
|
|
|
|
unique: true,
|
|
|
|
fields: ['injection_key'],
|
|
|
|
method: 'BTREE',
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
async function database_init() {
|
|
|
|
const force = false;
|
|
|
|
|
|
|
|
// Set up database schema
|
|
|
|
await Promise.all([
|
|
|
|
PayloadFireResults.sync({ force: force }),
|
2023-01-16 02:07:38 +00:00
|
|
|
Users.sync({ force: force }),
|
|
|
|
Secrets.sync({ force: force }),
|
2021-05-31 19:06:40 +00:00
|
|
|
CollectedPages.sync({ force: force }),
|
|
|
|
InjectionRequests.sync({ force: force }),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
sequelize,
|
|
|
|
PayloadFireResults,
|
|
|
|
CollectedPages,
|
|
|
|
InjectionRequests,
|
|
|
|
database_init,
|
2023-01-16 01:56:20 +00:00
|
|
|
savePayload,
|
2023-01-16 04:52:26 +00:00
|
|
|
Secrets,
|
2023-01-16 01:56:20 +00:00
|
|
|
Users
|
2021-05-31 19:06:40 +00:00
|
|
|
}
|