├── test
├── e2e
│ └── test-index.js
└── unit
│ └── test-index.js
├── src
├── options
│ ├── index.js
│ └── index.html
├── images
│ ├── app_icon_128.png
│ ├── app_icon_16.png
│ ├── icon-black.png
│ ├── icon-green.png
│ └── setting.svg
├── models
│ ├── sink.js
│ ├── source.js
│ ├── trace.js
│ ├── result.js
│ └── extension-ui-actions.js
├── popup
│ ├── detail.html
│ ├── index.html
│ ├── detail.js
│ └── index.js
├── content-scripts
│ └── index.js
├── manifest.json
├── styles
│ └── style.css
└── background
│ ├── poc-checker.js
│ ├── event-emitter.js
│ ├── debugger.js
│ ├── convert.js
│ ├── interceptor.js
│ ├── index.js
│ └── preload.js
├── chrome-store
├── screen1.png
├── screen2.png
├── screen3.png
├── screen4.png
└── screen5.png
├── .gitignore
├── .babelrc
├── LICENSE
├── README.md
├── scripts
└── zip.js
├── package.json
└── webpack.config.js
/test/e2e/test-index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/unit/test-index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/index.js:
--------------------------------------------------------------------------------
1 | console.debug('options');
--------------------------------------------------------------------------------
/chrome-store/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/chrome-store/screen1.png
--------------------------------------------------------------------------------
/chrome-store/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/chrome-store/screen2.png
--------------------------------------------------------------------------------
/chrome-store/screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/chrome-store/screen3.png
--------------------------------------------------------------------------------
/chrome-store/screen4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/chrome-store/screen4.png
--------------------------------------------------------------------------------
/chrome-store/screen5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/chrome-store/screen5.png
--------------------------------------------------------------------------------
/src/images/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/src/images/app_icon_128.png
--------------------------------------------------------------------------------
/src/images/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/src/images/app_icon_16.png
--------------------------------------------------------------------------------
/src/images/icon-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/src/images/icon-black.png
--------------------------------------------------------------------------------
/src/images/icon-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AsaiKen/dom-based-xss-finder/HEAD/src/images/icon-green.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .idea
4 | build
5 | dist
6 | dist-zip
7 | *.pem
8 | *.crx
9 | tmp/
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ],
5 | "plugins": [
6 | "@babel/plugin-transform-runtime"
7 | ]
8 | }
--------------------------------------------------------------------------------
/src/models/sink.js:
--------------------------------------------------------------------------------
1 | export default class Sink {
2 | constructor({ label, stacktrace}) {
3 | this.label = label;
4 | this.stacktrace = stacktrace;
5 | }
6 | }
--------------------------------------------------------------------------------
/src/models/source.js:
--------------------------------------------------------------------------------
1 | export default class Source {
2 | constructor({ label, stacktrace}) {
3 | this.label = label;
4 | this.stacktrace = stacktrace;
5 | }
6 | }
--------------------------------------------------------------------------------
/src/models/trace.js:
--------------------------------------------------------------------------------
1 | export default class Trace {
2 | constructor() {
3 | this.url = null;
4 | this.line = null;
5 | this.column = null;
6 | this.code = null;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/models/result.js:
--------------------------------------------------------------------------------
1 | export default class Result {
2 | constructor({ id, url, source, sink }) {
3 | this.id = id;
4 | this.url = url;
5 | this.source = source;
6 | this.sink = sink;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/options/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Option for DOM based XSS finder
6 |
7 |
8 | Option
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/models/extension-ui-actions.js:
--------------------------------------------------------------------------------
1 | export default {
2 | IS_RUNNING: 'IS_RUNNING',
3 | START: 'START',
4 | STOP: 'STOP',
5 | REMOVE: 'REMOVE',
6 | REMOVE_ALL: 'REMOVE_ALL',
7 | ADD_ALL: 'ADD_ALL',
8 | CHECK_AND_GENERATE_POC: 'CHECK_AND_GENERATE_POC',
9 | SET_POC: 'SET_POC',
10 | };
11 |
--------------------------------------------------------------------------------
/src/popup/detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Source CallStack
13 |
14 |
15 | Sink CallStack
16 |
17 |
18 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/content-scripts/index.js:
--------------------------------------------------------------------------------
1 | import actions from '../models/extension-ui-actions';
2 |
3 | if (!window.__dombasedxssfinder_contentscript) {
4 | window.__dombasedxssfinder_contentscript = true;
5 | setInterval(() => {
6 | const elements = document.querySelectorAll('.__dombasedxssfinder_result');
7 | if (elements.length > 0) {
8 | console.debug(elements);
9 | }
10 | const results = [];
11 | for (const e of elements) {
12 | const result = JSON.parse(e.textContent);
13 | if (/jquery|google/.test(result.source.stacktrace[0].url)) {
14 | continue;
15 | }
16 | results.push(result);
17 | }
18 | for (const e of elements) {
19 | e.parentElement.removeChild(e);
20 | }
21 | if (results.length > 0) {
22 | chrome.runtime.sendMessage({ action: actions.ADD_ALL, results });
23 | }
24 | }, 100);
25 | console.debug(`content-scripts at ${location.href}`);
26 | }
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DOM based XSS finder",
3 | "version": "1.0.0",
4 | "manifest_version": 2,
5 | "description": "A Chrome extension for finding DOM based XSS vulnerabilities",
6 | "permissions": [
7 | "storage",
8 | "webNavigation",
9 | "tabs",
10 | "*://*/",
11 | "debugger",
12 | "unlimitedStorage"
13 | ],
14 | "icons" : {
15 | "16": "images/app_icon_16.png",
16 | "128": "images/app_icon_128.png"
17 | },
18 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
19 | "browser_action": {
20 | "default_icon": "images/icon-black.png",
21 | "default_title": "DOM based XSS finder",
22 | "default_popup": "popup.html"
23 | },
24 | "background": {
25 | "scripts": [
26 | "background.js"
27 | ],
28 | "persistent": true
29 | },
30 | "options_ui": {
31 | "page": "options.html",
32 | "open_in_tab": true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Ken Asai
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DOM based XSS finder
2 |
3 | "DOM based XSS finder" is a Chrome extension that finds DOM based XSS vulnerabilities. Install it from the [Chrome Webstore](https://chrome.google.com/webstore/detail/dom-based-xss-finder/ngmdldjheklkdchgkgnjoaabgejcnnoi).
4 |
5 | Finding DOM based XSS can be bothersome. This extension can be helpful. This extension has the following features:
6 |
7 | - Notify if a user-input such as "location.href" leads to a dangerous JavaScript function such as "eval".
8 | - Fuzzing for user-inputs such as query, hash and referrer.
9 | - Generate a PoC that generates a alert prompt.
10 |
11 | ## Usage
12 |
13 | **This tool is a dynamic JavaScript tracer, not a static JavaScript scanner. So you must execute JavaScript by manual
14 | crawling with this extension starting.**
15 |
16 | - Click the icon and hit "Start".
17 | - Browse pages that you want to scan.
18 | - If the extension finds a possible vulnerability of DOM based XSS, the extension shows a entry for that url.
19 | - Click "Detail" in the entry. A popup window show a source and a sink of the possible vulnerability.
20 | - Click "Check and Generate PoC" in the popup window. You can fuzzing the url.
21 |
22 | ## License
23 |
24 | MIT
25 |
--------------------------------------------------------------------------------
/scripts/zip.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const fs = require('fs');
3 | const path = require('path');
4 | const zipFolder = require('zip-folder');
5 |
6 | const DEST_DIR = path.join(__dirname, '../dist');
7 | const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip');
8 |
9 | const extractExtensionData = () => {
10 | const extPackageJson = require('../package.json');
11 |
12 | return {
13 | name: extPackageJson.name,
14 | version: extPackageJson.version
15 | };
16 | };
17 |
18 | const makeDestZipDirIfNotExists = () => {
19 | if (!fs.existsSync(DEST_ZIP_DIR)) {
20 | fs.mkdirSync(DEST_ZIP_DIR);
21 | }
22 | };
23 |
24 | const buildZip = (src, dist, zipFilename) => {
25 | console.info(`Building ${zipFilename}...`);
26 |
27 | return new Promise((resolve, reject) => {
28 | zipFolder(src, path.join(dist, zipFilename), (err) => {
29 | if (err) {
30 | reject(err);
31 | } else {
32 | resolve();
33 | }
34 | });
35 | });
36 | };
37 |
38 | const main = () => {
39 | const { name, version } = extractExtensionData();
40 | const zipFilename = `${name}-v${version}.zip`;
41 |
42 | makeDestZipDirIfNotExists();
43 |
44 | buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
45 | .then(() => console.info('OK'))
46 | .catch(console.err);
47 | };
48 |
49 | main();
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dom-based-xss-finder",
3 | "version": "1.0.0",
4 | "description": "A Chrome extension for finding DOM based XSS vulnerabilities",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "NODE_ENV=development DEBUG=dom-based-xss-finder:* webpack --watch",
8 | "build": "NODE_ENV=production webpack",
9 | "dist": "NODE_ENV=production webpack && node scripts/zip.js",
10 | "test": "mocha test/**/test-*.js"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/AsaiKen/dom-based-xss-finder"
15 | },
16 | "keywords": [
17 | "chrome",
18 | "extension",
19 | "xss"
20 | ],
21 | "author": "Ken Asai",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@babel/runtime": "^7.7.7"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.7.7",
28 | "@babel/plugin-transform-runtime": "^7.7.6",
29 | "@babel/preset-env": "^7.7.7",
30 | "@fortawesome/fontawesome-free": "^5.12.0",
31 | "babel-loader": "^8.0.6",
32 | "bootstrap": "^4.4.1",
33 | "chai": "^4.2.0",
34 | "copy-webpack-plugin": "^5.1.1",
35 | "css-loader": "^3.4.0",
36 | "encoding-japanese": "^1.0.30",
37 | "html-webpack-plugin": "^3.2.0",
38 | "iconv-lite": "^0.5.0",
39 | "jquery": "^3.4.1",
40 | "mocha": "^6.2.2",
41 | "popper.js": "^1.16.0",
42 | "source-map": "^0.7.3",
43 | "style-loader": "^1.1.2",
44 | "webpack": "^4.41.5",
45 | "webpack-cli": "^3.3.10",
46 | "zip-folder": "^1.0.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/styles/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-size: 100%;
4 | color: #3C4858;
5 | width: 350px;
6 | max-height: 500px;
7 | }
8 |
9 | .header {
10 | background: #F9FAFC;
11 | height: 90px;
12 | display: flex;
13 | justify-content: flex-start;
14 | align-items: center;
15 | padding: 0 12px;
16 | font-weight: 500;
17 | }
18 |
19 | .main {
20 | font-size: 14px;
21 | padding: 0 12px;
22 | }
23 |
24 | .result {
25 | max-height: 400px;
26 | flex: 1;
27 | height: 100%;
28 | overflow: auto;
29 | overflow-x: hidden;
30 | display: flex;
31 | flex-direction: column-reverse;
32 | }
33 |
34 | .callstack {
35 | font-size: 15px;
36 | }
37 |
38 | button.btn {
39 | display: inline-block;
40 | font-weight: 400;
41 | line-height: 1.25;
42 | text-align: center;
43 | white-space: nowrap;
44 | vertical-align: middle;
45 | user-select: none;
46 | border: 1px solid transparent;
47 | cursor: pointer;
48 | letter-spacing: 1px;
49 | transition: all .15s ease;
50 | }
51 |
52 | button.btn.btn-sm {
53 | padding: .4rem .8rem;
54 | font-size: .8rem;
55 | border-radius: .2rem;
56 | height: 30px;
57 | }
58 |
59 | button.btn.btn-primary, button.btn.btn-primary:active:focus {
60 | color: #fff;
61 | background-color: #45C8F1;
62 | border-color: #45C8F1;
63 | }
64 |
65 | button.btn.btn-outline-primary, button.btn.btn-outline-primary:active:focus {
66 | color: #45C8F1;
67 | background-color: transparent;
68 | border-color: #45C8F1;
69 | }
70 |
71 | button.btn.btn-danger, button.btn.btn-danger:active:focus {
72 | color: #fff;
73 | background-color: #FF4949;
74 | border-color: #FF4949;
75 | }
76 |
77 | button.btn.btn-outline-danger, button.btn.btn-outline-danger:active:focus {
78 | color: #FF4949;
79 | background-color: transparent;
80 | border-color: #FF4949;
81 | }
82 |
--------------------------------------------------------------------------------
/src/background/poc-checker.js:
--------------------------------------------------------------------------------
1 | import Debugger from './debugger';
2 |
3 | export default class PocChecker {
4 | constructor(windowId, tabId, url, keyword) {
5 | this.windowId = windowId;
6 | this.tabId = tabId;
7 | this.debugger_ = new Debugger(tabId);
8 | this.url = url;
9 | this.keyword = keyword;
10 | this.detect = false;
11 | this._isIdle = false;
12 | }
13 |
14 | async start() {
15 | const debugger_ = this.debugger_;
16 | try {
17 | await debugger_.attach();
18 | console.debug('attached', { tabId: this.tabId });
19 | } catch (e) {
20 | console.error(e);
21 | return;
22 | }
23 |
24 | await debugger_.sendCommand('Page.enable');
25 | await debugger_.on('Page.javascriptDialogOpening', async({ message }) => {
26 | console.debug('message', message);
27 | if (message.includes(this.keyword)) {
28 | this.detect = true;
29 | }
30 | await debugger_.sendCommand('Page.handleJavaScriptDialog', { accept: true });
31 | });
32 | await debugger_.sendCommand('Page.setLifecycleEventsEnabled', { enabled: true });
33 | await debugger_.on('Page.lifecycleEvent', async({ name }) => {
34 | console.debug('lifecycleEvent', name);
35 | if (name === 'networkAlmostIdle') {
36 | await this.setIdle();
37 | }
38 | });
39 | await debugger_.sendCommand('Page.navigate', { url: this.url });
40 | console.debug('start', this.windowId);
41 | }
42 |
43 | isIdle() {
44 | return this._isIdle;
45 | }
46 |
47 | async setIdle() {
48 | console.debug('setIdle');
49 | this._isIdle = true;
50 | }
51 |
52 | stop() {
53 | return new Promise(async resolve => {
54 | chrome.windows.remove(this.windowId, () => {
55 | console.debug('remove', this.windowId);
56 | resolve();
57 | });
58 | });
59 | }
60 | };
--------------------------------------------------------------------------------
/src/background/event-emitter.js:
--------------------------------------------------------------------------------
1 | //https://gist.github.com/mudge/5830382
2 |
3 | /* Polyfill indexOf. */
4 | var indexOf;
5 |
6 | if (typeof Array.prototype.indexOf === 'function') {
7 | indexOf = function(haystack, needle) {
8 | return haystack.indexOf(needle);
9 | };
10 | } else {
11 | indexOf = function(haystack, needle) {
12 | var i = 0, length = haystack.length, idx = -1, found = false;
13 |
14 | while (i < length && !found) {
15 | if (haystack[i] === needle) {
16 | idx = i;
17 | found = true;
18 | }
19 |
20 | i++;
21 | }
22 |
23 | return idx;
24 | };
25 | }
26 |
27 |
28 | /* Polyfill EventEmitter. */
29 | var EventEmitter = function() {
30 | this.events = {};
31 | };
32 |
33 | EventEmitter.prototype.on = function(event, listener) {
34 | if (typeof this.events[event] !== 'object') {
35 | this.events[event] = [];
36 | }
37 |
38 | this.events[event].push(listener);
39 | };
40 |
41 | EventEmitter.prototype.removeListener = function(event, listener) {
42 | var idx;
43 |
44 | if (typeof this.events[event] === 'object') {
45 | idx = indexOf(this.events[event], listener);
46 |
47 | if (idx > -1) {
48 | this.events[event].splice(idx, 1);
49 | }
50 | }
51 | };
52 |
53 | EventEmitter.prototype.emit = function(event) {
54 | var i, listeners, length, args = [].slice.call(arguments, 1);
55 |
56 | if (typeof this.events[event] === 'object') {
57 | listeners = this.events[event].slice();
58 | length = listeners.length;
59 |
60 | for (i = 0; i < length; i++) {
61 | listeners[i].apply(this, args);
62 | }
63 | }
64 | };
65 |
66 | EventEmitter.prototype.once = function(event, listener) {
67 | this.on(event, function g() {
68 | this.removeListener(event, g);
69 | listener.apply(this, arguments);
70 | });
71 | };
72 |
73 | export default EventEmitter;
--------------------------------------------------------------------------------
/src/background/debugger.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from './event-emitter';
2 |
3 | const DEBUGGER_PROTOCOL_VERSION = '1.3';
4 |
5 | export default class Debugger extends EventEmitter {
6 | constructor(tabId) {
7 | super();
8 | this.tabId = tabId;
9 | this.boundOnEventHandler = this.onEventHandler.bind(this);
10 | }
11 |
12 | attach() {
13 | return new Promise((resolve, reject) => {
14 | const target = { tabId: this.tabId };
15 | chrome.debugger.attach(target, DEBUGGER_PROTOCOL_VERSION, () => {
16 | if (chrome.runtime.lastError) {
17 | //attached
18 | reject(chrome.runtime.lastError);
19 | return;
20 | }
21 | chrome.debugger.onEvent.addListener(this.boundOnEventHandler);
22 | resolve();
23 | });
24 | });
25 | }
26 |
27 | onEventHandler(source, method, params) {
28 | // console.debug(source, method, params);
29 | if (source.tabId === this.tabId) {
30 | this.emit(method, params);
31 | }
32 | }
33 |
34 | detach() {
35 | return new Promise((resolve, reject) => {
36 | chrome.debugger.onEvent.removeListener(this.boundOnEventHandler);
37 | const target = { tabId: this.tabId };
38 | chrome.debugger.detach(target, () => {
39 | if (chrome.runtime.lastError && chrome.runtime.lastError.message) {
40 | console.debug(chrome.runtime.lastError.message);
41 | }
42 | resolve();
43 | });
44 | });
45 | }
46 |
47 | sendCommand(command, params) {
48 | return new Promise((resolve, reject) => {
49 | const target = { tabId: this.tabId };
50 | chrome.debugger.sendCommand(target, command, params, result => {
51 | if (chrome.runtime.lastError && chrome.runtime.lastError.message) {
52 | console.debug(chrome.runtime.lastError.message);
53 | }
54 | resolve(result);
55 | });
56 | });
57 | }
58 | };
--------------------------------------------------------------------------------
/src/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/images/setting.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const CopyPlugin = require('copy-webpack-plugin');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 |
6 | const { NODE_ENV = 'development' } = process.env;
7 |
8 | const base = {
9 | context: __dirname,
10 | entry: {
11 | background: './src/background/index.js',
12 | 'content-script': './src/content-scripts/index.js',
13 | popup: './src/popup/index.js',
14 | detail: './src/popup/detail.js',
15 | options: './src/options/index.js'
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js$/,
21 | exclude: /node_modules/,
22 | loader: 'babel-loader'
23 | },
24 | {
25 | test: /\.css$/,
26 | use: [
27 | 'style-loader',
28 | 'css-loader'
29 | ]
30 | }
31 | ]
32 | },
33 | plugins: [
34 | new CopyPlugin([
35 | { from: './src/manifest.json', to: './manifest.json' },
36 | { from: './src/images', to: 'images' },
37 | { from: './src/background/preload.js', to: './preload.js' }
38 | ]),
39 | new HtmlWebpackPlugin({
40 | template: './src/popup/index.html',
41 | chunks: ['popup'],
42 | filename: 'popup.html'
43 | }),
44 | new HtmlWebpackPlugin({
45 | template: './src/options/index.html',
46 | chunks: ['options'],
47 | filename: 'options.html'
48 | }),
49 | new HtmlWebpackPlugin({
50 | template: './src/popup/detail.html',
51 | chunks: ['detail'],
52 | filename: 'detail.html'
53 | }),
54 | new webpack.DefinePlugin({
55 | 'process.env': {
56 | NODE_ENV: JSON.stringify(NODE_ENV)
57 | }
58 | })
59 | ],
60 | node: {
61 | fs: "empty"
62 | }
63 | };
64 |
65 | const development = {
66 | ...base,
67 | output: {
68 | path: path.join(__dirname, 'build'),
69 | filename: '[name].js'
70 | },
71 | mode: 'development',
72 | devtool: '#eval-module-source-map',
73 | plugins: [
74 | ...base.plugins,
75 | new webpack.HotModuleReplacementPlugin()
76 | ]
77 | };
78 |
79 | const production = {
80 | ...base,
81 | output: {
82 | path: path.join(__dirname, 'dist'),
83 | filename: '[name].js'
84 | },
85 | mode: 'production',
86 | devtool: '#source-map',
87 | plugins: [
88 | ...base.plugins,
89 | new webpack.LoaderOptionsPlugin({
90 | minimize: true,
91 | debug: false
92 | })
93 | ]
94 | };
95 |
96 | if (NODE_ENV === 'development') {
97 | module.exports = development;
98 | } else {
99 | module.exports = production;
100 | }
101 |
--------------------------------------------------------------------------------
/src/popup/detail.js:
--------------------------------------------------------------------------------
1 | import "bootstrap/dist/css/bootstrap.min.css";
2 | import "bootstrap";
3 | import "../styles/style.css";
4 | import actions from '../models/extension-ui-actions';
5 |
6 | const resultId = new URL(location.href).searchParams.get('resultId');
7 |
8 | const pocButton = document.querySelector('#poc-button');
9 | pocButton.addEventListener('click', function() {
10 | chrome.runtime.sendMessage({ action: actions.CHECK_AND_GENERATE_POC, resultId });
11 | });
12 |
13 | function setPocUrl(url) {
14 | const pocDiv = document.getElementById('poc');
15 | pocDiv.classList.remove('d-none');
16 | const pocUrlDiv = document.getElementById('poc-url');
17 | if (url) {
18 | [...pocUrlDiv.children].forEach(c => pocUrlDiv.removeChild(c));
19 | const anchor = document.createElement('a');
20 | anchor.href = url;
21 | anchor.target = '_blank';
22 | anchor.textContent = url;
23 | pocUrlDiv.appendChild(anchor);
24 | } else {
25 | pocUrlDiv.textContent = 'Not Found';
26 | }
27 | }
28 |
29 | chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
30 | if (msg.action === actions.SET_POC && msg.resultId === Number(resultId)) {
31 | const pocUrl = msg.pocUrl;
32 | setPocUrl(pocUrl);
33 | sendResponse();
34 | }
35 | });
36 |
37 | const removeButton = document.querySelector('#remove-button');
38 | removeButton.addEventListener('click', function() {
39 | chrome.runtime.sendMessage({ action: actions.REMOVE, resultId }, () => {
40 | window.close();
41 | });
42 | });
43 |
44 | chrome.storage.local.get('results', async items => {
45 | let results = items.results || [];
46 | const result = results.find(r => r.id === Number(resultId));
47 | if (result) {
48 | document.title = `#${result.id} ${result.url}`;
49 |
50 | const sourceDiv = document.getElementById('source');
51 | const source = result.source;
52 | document.getElementById('source-label').textContent = source.label;
53 | const sourceStacktrace = source.stacktrace;
54 | for (let i = 0; i < sourceStacktrace.length; i++) {
55 | const trace = sourceStacktrace[i];
56 | const posDiv = document.createElement('li');
57 | posDiv.textContent = `at ${trace.url}:${trace.line}:${trace.column}`;
58 | sourceDiv.appendChild(posDiv);
59 | const codeDiv = document.createElement('code');
60 | codeDiv.textContent = `${trace.code}`;
61 | sourceDiv.appendChild(codeDiv);
62 | }
63 |
64 | const sinkDiv = document.getElementById('sink');
65 | const sink = result.sink;
66 | document.getElementById('sink-label').textContent = sink.label;
67 | const sinkStacktrace = sink.stacktrace;
68 | for (let i = 0; i < sinkStacktrace.length; i++) {
69 | const trace = sinkStacktrace[i];
70 | const posDiv = document.createElement('li');
71 | posDiv.textContent = `at ${trace.url}:${trace.line}:${trace.column}`;
72 | sinkDiv.appendChild(posDiv);
73 | const codeDiv = document.createElement('code');
74 | codeDiv.textContent = `${trace.code}`;
75 | sinkDiv.appendChild(codeDiv);
76 | }
77 |
78 | if (Object.keys(result).includes('pocUrl')) {
79 | setPocUrl(result.pocUrl);
80 | }
81 | }
82 | });
83 |
--------------------------------------------------------------------------------
/src/popup/index.js:
--------------------------------------------------------------------------------
1 | import "bootstrap/dist/css/bootstrap.min.css";
2 | import "bootstrap";
3 | import "../styles/style.css";
4 | import '@fortawesome/fontawesome-free/js/fontawesome';
5 | import '@fortawesome/fontawesome-free/js/solid';
6 | import '@fortawesome/fontawesome-free/js/regular';
7 | import actions from '../models/extension-ui-actions';
8 | import {name} from '../../package';
9 |
10 | class Popup {
11 | constructor() {
12 | this.port = chrome.extension.connect({ name });
13 | this.startButton = document.querySelector('#enable-button');
14 | this.stopButton = document.querySelector('#disable-button');
15 | // this.settingButton = document.querySelector('#setting-button');
16 | this.removeAllButton = document.querySelector('#remove-all-button');
17 |
18 | this.port.postMessage({ action: actions.IS_RUNNING });
19 | this.port.onMessage.addListener(({ isRunning }) => {
20 | if (typeof isRunning === 'boolean') {
21 | if (isRunning) {
22 | this.showStartButton();
23 | } else {
24 | this.showStopButton();
25 | }
26 | }
27 | });
28 |
29 | this.startButton.addEventListener('click', () => {
30 | this.start();
31 | });
32 | this.stopButton.addEventListener('click', () => {
33 | this.stop();
34 | });
35 |
36 | // this.settingButton.addEventListener('click', () => {
37 | // chrome.runtime.openOptionsPage();
38 | // });
39 |
40 | this.removeAllButton.addEventListener('click', () => {
41 | this.removeAll();
42 | });
43 |
44 | this.loadResults();
45 | }
46 |
47 | loadResults() {
48 | chrome.storage.local.get('results', items => {
49 | const results = items.results || [];
50 | for (const result of results.sort((a, b) => a.id - b.id)) {
51 | this.add(result);
52 | }
53 | });
54 | }
55 |
56 | showStartButton() {
57 | this.startButton.classList.add('d-none');
58 | this.stopButton.classList.remove('d-none');
59 | }
60 |
61 | start() {
62 | this.showStartButton();
63 | this.port.postMessage({ action: actions.START });
64 | }
65 |
66 | showStopButton() {
67 | this.startButton.classList.remove('d-none');
68 | this.stopButton.classList.add('d-none');
69 | }
70 |
71 | stop() {
72 | this.showStopButton();
73 | this.port.postMessage({ action: actions.STOP });
74 | }
75 |
76 | removeAll() {
77 | console.debug('removeAll');
78 | this.port.postMessage({ action: actions.REMOVE_ALL });
79 | const results = document.querySelectorAll('.row-result');
80 | for (const result of results) {
81 | result.parentElement.removeChild(result);
82 | }
83 | }
84 |
85 | add(result) {
86 | const id = result.id;
87 | const url = result.url;
88 | const resultContainer = document.querySelector('#container-result');
89 | const template = document.querySelector('#row-result-template');
90 | const content = template.content;
91 | content.querySelector('.row-result').id = 'result-' + id;
92 | content.querySelector('.col-result-id').innerText = id;
93 | const resultUrl = content.querySelector('.col-result-url');
94 | resultUrl.textContent = url;
95 | content.querySelector('.result-detail-button').dataset.resultId = id;
96 | content.querySelector('.result-remove-button').dataset.resultId = id;
97 | const element = document.importNode(content, true);
98 | const detailButton = element.querySelector('.result-detail-button');
99 | detailButton.addEventListener('click', () => {
100 | this.detail(result.id);
101 | });
102 | const removeButton = element.querySelector('.result-remove-button');
103 | removeButton.addEventListener('click', () => {
104 | this.remove(result.id);
105 | });
106 | resultContainer.appendChild(element);
107 | }
108 |
109 | detail(resultId) {
110 | chrome.windows.create({
111 | url: chrome.runtime.getURL("detail.html") + '?resultId=' + resultId,
112 | type: "popup"
113 | });
114 | }
115 |
116 | remove(resultId) {
117 | this.port.postMessage({ action: actions.REMOVE, resultId });
118 | const result = document.getElementById('result-' + resultId);
119 | if (result) {
120 | result.parentElement.removeChild(result);
121 | }
122 | }
123 | }
124 |
125 | window.__dombasedxssfinder_popup = new Popup();
126 |
--------------------------------------------------------------------------------
/src/background/convert.js:
--------------------------------------------------------------------------------
1 | import {transform} from '@babel/core';
2 |
3 | export default function(src) {
4 | const visitedKey = '__dombasedxssfinder_visited_key__';
5 | const plugin = ({ types: t }) => {
6 | function callExpression(callee, arguments_) {
7 | const e = t.callExpression(callee, arguments_);
8 | e[visitedKey] = true;
9 | return e;
10 | }
11 |
12 | const visitor = {
13 | BinaryExpression: {
14 | enter: (nodePath) => {
15 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
16 | return;
17 | }
18 |
19 | const { left, operator, right } = nodePath.node;
20 | let newAst;
21 | if (operator === '+') {
22 | // a + b -> __dombasedxssfinder_plus(a, b)
23 | newAst = callExpression(
24 | t.identifier('__dombasedxssfinder_plus'),
25 | [left, right]
26 | );
27 | } else if (operator === '==') {
28 | newAst = callExpression(
29 | t.identifier('__dombasedxssfinder_equal'),
30 | [left, right]
31 | );
32 | } else if (operator === '!=') {
33 | newAst = callExpression(
34 | t.identifier('__dombasedxssfinder_notEqual'),
35 | [left, right]
36 | );
37 | } else if (operator === '===') {
38 | newAst = callExpression(
39 | t.identifier('__dombasedxssfinder_strictEqual'),
40 | [left, right]
41 | );
42 | } else if (operator === '!==') {
43 | newAst = callExpression(
44 | t.identifier('__dombasedxssfinder_strictNotEqual'),
45 | [left, right]
46 | );
47 | }
48 | if (newAst) {
49 | nodePath.replaceWith(newAst);
50 | nodePath[visitedKey] = true;
51 | }
52 | },
53 | },
54 | AssignmentExpression: {
55 | enter: (nodePath) => {
56 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
57 | return;
58 | }
59 |
60 | let { left, operator, right } = nodePath.node;
61 | if (operator === '+=') {
62 | // a += b -> a = __dombasedxssfinder_plus(a, b)
63 | right = callExpression(
64 | t.identifier('__dombasedxssfinder_plus'),
65 | [left, right]
66 | );
67 | } else if (operator.length >= 2 && operator.endsWith('=')) {
68 | const subOp = operator.slice(0, -1);
69 | // a -= b -> a = a - b
70 | right = t.binaryExpression(subOp, left, right);
71 | }
72 | let newAst;
73 | if (left.type === 'MemberExpression') {
74 | // a.b = c -> __dombasedxssfinder_put(a, b, c)
75 | const { object, property, computed } = left;
76 | let key;
77 | if (computed) { // a[b], a['b']
78 | key = property;
79 | } else { // a.b
80 | key = t.stringLiteral(property.name);
81 | }
82 | newAst = callExpression(
83 | t.identifier('__dombasedxssfinder_put'),
84 | [object, key, right]
85 | );
86 | } else {
87 | const assignmentExpression = t.assignmentExpression("=", left, right);
88 | assignmentExpression[visitedKey] = true;
89 | newAst = assignmentExpression;
90 | }
91 | nodePath.replaceWith(newAst);
92 | nodePath[visitedKey] = true;
93 | }
94 | },
95 | MemberExpression: {
96 | enter: (nodePath) => {
97 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
98 | return;
99 | }
100 |
101 | const { object, property, computed } = nodePath.node;
102 | let key;
103 | if (computed) { // a[b], a['b']
104 | key = property;
105 | } else { // a.b
106 | key = t.stringLiteral(property.name);
107 | }
108 | const newAst = callExpression(
109 | t.identifier('__dombasedxssfinder_get'),
110 | [object, key]
111 | );
112 | nodePath.replaceWith(newAst);
113 | nodePath[visitedKey] = true;
114 | }
115 | },
116 | NewExpression: {
117 | enter: (nodePath) => {
118 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
119 | return;
120 | }
121 |
122 | const o = nodePath.node;
123 | const callee = o.callee;
124 | const arguments_ = o.arguments;
125 | if (callee.name === 'Function') {
126 | const newAst = callExpression(
127 | t.identifier('__dombasedxssfinder_new_Function'),
128 | arguments_
129 | );
130 | nodePath.replaceWith(newAst);
131 | nodePath[visitedKey] = true;
132 | }
133 | }
134 | },
135 | UnaryExpression: {
136 | enter: (nodePath) => {
137 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
138 | return;
139 | }
140 |
141 | const { operator, argument } = nodePath.node;
142 | if (operator === 'typeof') {
143 | let newAst;
144 | if (argument.type === 'Identifier') {
145 | const unaryExpression = t.unaryExpression('typeof', argument, true);
146 | unaryExpression[visitedKey] = true;
147 | const binaryExpression = t.binaryExpression('===', unaryExpression, t.stringLiteral('undefined'));
148 | binaryExpression[visitedKey] = true;
149 | newAst = callExpression(
150 | t.identifier('__dombasedxssfinder_typeof'),
151 | [
152 | // aが未定義の場合、typeof aは通過するが、f(a)はエラーになる。その対応。
153 | t.conditionalExpression(
154 | binaryExpression,
155 | t.identifier('undefined'),
156 | argument
157 | )
158 | ]
159 | );
160 | } else {
161 | newAst = callExpression(
162 | t.identifier('__dombasedxssfinder_typeof'),
163 | [argument]
164 | );
165 | }
166 | nodePath.replaceWith(newAst);
167 | nodePath[visitedKey] = true;
168 | } else if (operator === 'delete') {
169 | if (argument.type === 'MemberExpression') {
170 | // delete __dombasedxssfinder_get(a, 'b')だとdeleteされないので、MemberExpressionを残す
171 | argument[visitedKey] = true;
172 | }
173 | }
174 | }
175 | },
176 | CallExpression: {
177 | enter: (nodePath) => {
178 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
179 | return;
180 | }
181 |
182 | const o = nodePath.node;
183 | const callee = o.callee;
184 | const arguments_ = o.arguments;
185 | let newAst;
186 | if (callee.type === 'MemberExpression') {
187 | const { object, property, computed } = callee;
188 | let key;
189 | if (computed) { // a[b], a['b']
190 | key = property;
191 | } else { // a.b
192 | key = t.stringLiteral(property.name);
193 | }
194 | newAst = callExpression(
195 | t.identifier('__dombasedxssfinder_property_call'),
196 | [object, key, ...arguments_]
197 | );
198 | } else {
199 | newAst = callExpression(
200 | t.identifier('__dombasedxssfinder_call'),
201 | [callee, ...arguments_]
202 | );
203 | }
204 | nodePath.replaceWith(newAst);
205 | nodePath[visitedKey] = true;
206 | }
207 | },
208 | UpdateExpression: {
209 | enter: (nodePath) => {
210 | if (nodePath[visitedKey] || nodePath.node[visitedKey]) {
211 | return;
212 | }
213 |
214 | const { argument } = nodePath.node;
215 | if (argument.type === 'MemberExpression') {
216 | // __dombasedxssfinder_get(this, "activeNums")++;はエラーになるので、MemberExpressionを残す
217 | argument[visitedKey] = true;
218 | }
219 | }
220 | },
221 | };
222 | return { visitor };
223 | };
224 |
225 | try {
226 | const { code, map } = transform(src, {
227 | parserOpts: { strictMode: false },
228 | plugins: [plugin],
229 | configFile: false,
230 | sourceMaps: true,
231 | retainLines: true,
232 | compact: false,
233 | });
234 | // console.debug('map', map);
235 | return { code, map };
236 | } catch (e) {
237 | console.error(e);
238 | return src;
239 | }
240 | };
--------------------------------------------------------------------------------
/src/background/interceptor.js:
--------------------------------------------------------------------------------
1 | import Debugger from './debugger';
2 | import convert from './convert';
3 | import iconv from 'iconv-lite';
4 | import Encoding from 'encoding-japanese';
5 |
6 | let PRELOAD_SOURCE = null;
7 |
8 | export default class Interceptor {
9 | constructor() {
10 | this.isRunning = false;
11 | /** @type {Debugger[]} */
12 | this.debuggers = [];
13 | this.sourceMaps = {};
14 | this.jsCache = {};
15 | this.bodyMaps = {};
16 |
17 | this.boundOnCreatedHandler = this.onCreatedHandler.bind(this);
18 | this.boundOnBeforeNavigateHandler = this.onBeforeNavigateHandler.bind(this);
19 | this.boundOnDOMContentLoadedHandler = this.onDOMContentLoadedHandler.bind(this);
20 | this.boundOnDetachHandler = this.onDetachHandler.bind(this);
21 | }
22 |
23 | async start() {
24 | if (PRELOAD_SOURCE === null) {
25 | const url = chrome.runtime.getURL("preload.js");
26 | const response = await fetch(url);
27 | PRELOAD_SOURCE = await response.text();
28 | }
29 | this.isRunning = true;
30 | chrome.windows.getAll({ populate: true }, async windows => {
31 | for (const w of windows) {
32 | for (const tab of w.tabs) {
33 | // console.debug('tab', tab);
34 | await this.attach({ tabId: tab.id, url: tab.url });
35 | }
36 | }
37 | });
38 | chrome.tabs.onCreated.addListener(this.boundOnCreatedHandler);
39 | chrome.webNavigation.onBeforeNavigate.addListener(this.boundOnBeforeNavigateHandler);
40 | chrome.webNavigation.onDOMContentLoaded.addListener(this.boundOnDOMContentLoadedHandler);
41 | chrome.debugger.onDetach.addListener(this.boundOnDetachHandler);
42 | }
43 |
44 | async onCreatedHandler({ id, url }) {
45 | console.debug('onCreated', { id, url });
46 | await this.attach({ tabId: id, url });
47 | }
48 |
49 | async onBeforeNavigateHandler({ tabId, frameId, url }) {
50 | console.debug('onBeforeNavigate', { tabId, frameId, url });
51 | if (frameId === 0) {
52 | // mainframe navigated
53 | for (const key of Object.keys(this.sourceMaps)) {
54 | const o = JSON.parse(key);
55 | if (o.tabId === tabId) {
56 | // console.debug('remove sourceMaps', key);
57 | delete this.sourceMaps[key];
58 | }
59 | }
60 | for (const key of Object.keys(this.bodyMaps)) {
61 | const o = JSON.parse(key);
62 | if (o.tabId === tabId) {
63 | // console.debug('remove sourceMaps', key);
64 | delete this.bodyMaps[key];
65 | }
66 | }
67 | }
68 | }
69 |
70 | async onDOMContentLoadedHandler({ tabId, url }) {
71 | console.debug('onDOMContentLoaded', { tabId, url });
72 | chrome.tabs.executeScript(tabId, { file: 'content-script.js', allFrames: true });
73 | }
74 |
75 | async onDetachHandler({ tabId }) {
76 | console.debug('onDetach', { tabId });
77 | if (this.isRunning) {
78 | for (const debugger_ of this.debuggers) {
79 | if (debugger_.tabId === tabId) {
80 | chrome.tabs.get(tabId, async() => {
81 | if (chrome.runtime.lastError) {
82 | // closed
83 | return;
84 | }
85 | try {
86 | await debugger_.attach();
87 | console.debug('re-attached', { tabId });
88 | } catch (e) {
89 | console.error(e);
90 | }
91 | });
92 | break;
93 | }
94 | }
95 | }
96 | }
97 |
98 | async attach({ tabId, url }) {
99 | if (url === '' || url.startsWith('http://') || url.startsWith('https://')) {
100 | if (this.debuggers.some(d => d.tabId === tabId)) {
101 | // already attached
102 | } else {
103 | const debugger_ = new Debugger(tabId);
104 | try {
105 | await debugger_.attach();
106 | console.debug('attached', { tabId });
107 | } catch (e) {
108 | console.error(e);
109 | return;
110 | }
111 | await this.setInterceptor(debugger_);
112 | this.debuggers.push(debugger_);
113 | chrome.tabs.executeScript(tabId, { file: 'content-script.js', allFrames: true }, () => {
114 | if (chrome.runtime.lastError && chrome.runtime.lastError.message) {
115 | // console.debug(chrome.runtime.lastError.message);
116 | }
117 | });
118 | }
119 | }
120 | };
121 |
122 | async stop() {
123 | this.isRunning = false;
124 | for (const debugger_ of this.debuggers) {
125 | await debugger_.sendCommand('Network.clearBrowserCache');
126 | await debugger_.detach();
127 | console.debug('detached', { tabId: debugger_.tabId });
128 | }
129 | this.debuggers = [];
130 | this.sourceMaps = {};
131 | this.jsCache = {};
132 | this.bodyMaps = {};
133 | chrome.tabs.onCreated.removeListener(this.boundOnCreatedHandler);
134 | chrome.webNavigation.onBeforeNavigate.removeListener(this.boundOnBeforeNavigateHandler);
135 | chrome.webNavigation.onDOMContentLoaded.removeListener(this.boundOnDOMContentLoadedHandler);
136 | chrome.debugger.onDetach.removeListener(this.boundOnDetachHandler);
137 | }
138 |
139 | /**
140 | * set interception
141 | * @param debugger_ {Debugger}
142 | * @returns {Promise}
143 | */
144 | async setInterceptor(debugger_) {
145 | await debugger_.sendCommand('Page.enable');
146 | await debugger_.sendCommand('Page.addScriptToEvaluateOnNewDocument', { source: PRELOAD_SOURCE });
147 |
148 | await debugger_.sendCommand('Network.enable');
149 | await debugger_.sendCommand('Network.setRequestInterception', {
150 | patterns: [
151 | {
152 | urlPattern: '*',
153 | resourceType: 'Document',
154 | interceptionStage: 'HeadersReceived'
155 | },
156 | {
157 | urlPattern: '*',
158 | resourceType: 'Script',
159 | interceptionStage: 'HeadersReceived'
160 | }
161 |
162 | ],
163 | });
164 | await debugger_.sendCommand('Network.clearBrowserCache');
165 |
166 | await debugger_.on('Network.requestIntercepted', async({ interceptionId, resourceType, responseStatusCode, responseHeaders, request }) => {
167 | if (responseStatusCode === 200 && ['Document', 'Script'].includes(resourceType)
168 | && (request.url.startsWith('http://') || request.url.startsWith('https://'))) {
169 | // OK
170 | } else {
171 | await debugger_.sendCommand('Network.continueInterceptedRequest', { interceptionId });
172 | return;
173 | }
174 | if (resourceType === 'Script' && this.jsCache[request.url]) {
175 | // console.debug('cache hit', request.url);
176 | const { start, end, map, rawResponse, body } = this.jsCache[request.url];
177 | this.setSourceMap(debugger_.tabId, request.url, start, end, map);
178 | this.setBodyMap(debugger_.tabId, request.url, body);
179 | await debugger_.sendCommand('Network.continueInterceptedRequest', {
180 | interceptionId,
181 | rawResponse,
182 | });
183 | return;
184 | }
185 |
186 | let interceptTime = Date.now();
187 | const { body, base64Encoded } = await debugger_.sendCommand(
188 | 'Network.getResponseBodyForInterception',
189 | { interceptionId },
190 | );
191 | const headerLines = [];
192 | for (const key of Object.keys(responseHeaders)) {
193 | if (key.toLowerCase() === 'content-type') {
194 | if (responseHeaders[key].toLowerCase().includes('text') || responseHeaders[key].toLowerCase().includes('javascript')) {
195 | // OK
196 | } else {
197 | // not text
198 | await debugger_.sendCommand('Network.continueInterceptedRequest', { interceptionId });
199 | return;
200 | }
201 | }
202 | headerLines.push(`${key}: ${responseHeaders[key]}`);
203 | }
204 | let originalBodyStr;
205 | if (base64Encoded) {
206 | // assume utf8
207 | originalBodyStr = Buffer.from(body, 'base64').toString();
208 | } else {
209 | originalBodyStr = body;
210 | }
211 |
212 | let encoding = null;
213 | if (base64Encoded) {
214 | for (const key of Object.keys(responseHeaders)) {
215 | const value = responseHeaders[key];
216 | if (key.toLowerCase() === 'content-type' && value.includes('charset=')) {
217 | const m = value.match(/charset=['"]?([\w-]+)/);
218 | if (m) {
219 | encoding = m[1].trim();
220 | // console.debug('encoding', encoding);
221 | }
222 | }
223 | }
224 | if (resourceType === 'Document') {
225 | if (originalBodyStr.includes(`charset=`)) {
226 | const m = originalBodyStr.match(/charset=['"]?([\w-]+)/);
227 | if (m) {
228 | encoding = m[1].trim();
229 | // console.debug('encoding', encoding);
230 | }
231 | }
232 | }
233 | if (!encoding) {
234 | // auto-detect
235 | encoding = Encoding.detect(Buffer.from(body, 'base64'));
236 | // console.debug('encoding', encoding);
237 | }
238 | if (encoding) {
239 | originalBodyStr = iconv.decode(Buffer.from(body, 'base64'), encoding);
240 | }
241 | }
242 |
243 | // console.debug('originalBodyStr', originalBodyStr);
244 | let newBodyStr = null;
245 | let start = null;
246 | let end = null;
247 | let map = null;
248 | let convertTime = Date.now();
249 | if (resourceType === 'Document') {
250 | newBodyStr = originalBodyStr;
251 | const scriptTagStrs = originalBodyStr.match(/]*?>[\s\S]+?<\/script>/ig);
252 | for (const scriptTagStr of scriptTagStrs || []) {
253 | const originalCode = scriptTagStr.match(/]*?>(?:\s*\s*)?<\/script>/)[1];
254 | const converted = convert(originalCode);
255 | const code = converted.code;
256 | start = newBodyStr.indexOf(originalCode);
257 | end = start + code.length + 1;
258 | map = converted.map;
259 | newBodyStr = newBodyStr.replace(originalCode, code);
260 | this.setSourceMap(debugger_.tabId, request.url, start, end, map);
261 | }
262 | } else if (resourceType === 'Script') {
263 | const converted = convert(originalBodyStr);
264 | const code = converted.code;
265 | newBodyStr = code;
266 | start = 0;
267 | end = code.length + 1;
268 | map = converted.map;
269 | this.setSourceMap(debugger_.tabId, request.url, start, end, map);
270 | } else {
271 | throw new Error();
272 | }
273 | // console.debug('newBodyStr', newBodyStr);
274 | convertTime = Date.now() - convertTime;
275 | console.debug(request.url, 'convert', `${convertTime} ms`);
276 |
277 | let rawResponse;
278 | if (encoding) {
279 | const bodyBuf = iconv.encode(newBodyStr, encoding);
280 | rawResponse = Buffer.concat([Buffer.from(`HTTP/1.1 200 OK\r\n${headerLines.join('\r\n')}\r\n\r\n`), bodyBuf]).toString('base64');
281 | } else {
282 | rawResponse = Buffer.from(`HTTP/1.1 200 OK\r\n${headerLines.join('\r\n')}\r\n\r\n${newBodyStr}`).toString('base64');
283 | }
284 |
285 | if (resourceType === 'Script') {
286 | this.jsCache[request.url] = { start, end, map, rawResponse, body: newBodyStr };
287 | setTimeout(() => delete this.jsCache[request.url], 1000 * 60 * 60 * 24);
288 | }
289 | this.setBodyMap(debugger_.tabId, request.url, newBodyStr);
290 |
291 | await debugger_.sendCommand('Network.continueInterceptedRequest', {
292 | interceptionId,
293 | rawResponse,
294 | });
295 | interceptTime = Date.now() - interceptTime;
296 | console.debug(request.url, 'intercept', `${interceptTime} ms`);
297 | });
298 | }
299 |
300 | setSourceMap(tabId, url, start, end, map) {
301 | const key = JSON.stringify({ tabId, url, start, end });
302 | this.sourceMaps[key] = map;
303 | }
304 |
305 | setBodyMap(tabId, url, body) {
306 | const key = JSON.stringify({ tabId, url });
307 | this.bodyMaps[key] = body;
308 | }
309 | };
--------------------------------------------------------------------------------
/src/background/index.js:
--------------------------------------------------------------------------------
1 | import actions from '../models/extension-ui-actions';
2 | import Interceptor from './interceptor';
3 | import PocChecker from './poc-checker';
4 | import {name} from '../../package';
5 | import sourceMap from 'source-map';
6 |
7 | sourceMap.SourceMapConsumer.initialize({
8 | "lib/mappings.wasm": "https://unpkg.com/source-map@0.7.3/lib/mappings.wasm"
9 | });
10 |
11 | const KEYWORD = '11111111';
12 |
13 | class Background {
14 | constructor() {
15 | this.interceptor = new Interceptor();
16 |
17 | chrome.extension.onConnect.addListener(port => {
18 | if (port.name === name) {
19 | console.debug('port connected');
20 | port.onMessage.addListener(msg => {
21 | console.debug('port message received', msg);
22 | if (msg && msg.action) {
23 | if (msg.action === actions.IS_RUNNING) {
24 | port.postMessage({ isRunning: this.isRunning() });
25 | console.debug('isRunning', this.isRunning());
26 | } else if (msg.action === actions.START) {
27 | this.start();
28 | } else if (msg.action === actions.STOP) {
29 | this.stop();
30 | } else if (msg.action === actions.REMOVE) {
31 | this.remove(msg);
32 | } else if (msg.action === actions.REMOVE_ALL) {
33 | this.removeAll();
34 | }
35 | }
36 | });
37 | }
38 | });
39 | chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
40 | console.debug('message received', msg);
41 | if (msg.action === actions.ADD_ALL) {
42 | msg.tabId = sender.tab.id;
43 | this.addAll(msg);
44 | } else if (msg.action === actions.REMOVE) {
45 | this.remove(msg);
46 | } else if (msg.action === actions.CHECK_AND_GENERATE_POC) {
47 | this.checkAndGeneratePoc(msg);
48 | }
49 | sendResponse();
50 | });
51 | }
52 |
53 | async checkAndGeneratePoc({ resultId }) {
54 | console.debug('checkAndGeneratePoc', resultId);
55 | const isRunning = this.isRunning();
56 | if (isRunning) {
57 | await this.stop();
58 | }
59 | const result = await this.getResult(resultId);
60 | result.pocUrl = null;
61 | console.debug('result', result);
62 | const urls = this.getPocUrls(result);
63 | for (const url of urls) {
64 | const detect = await this.checkOne(url);
65 | if (detect) {
66 | result.pocUrl = url;
67 | break;
68 | }
69 | }
70 | await this.updateResult(result);
71 | if (isRunning) {
72 | await this.start();
73 | }
74 | }
75 |
76 | updateResult(result) {
77 | return new Promise(resolve => {
78 | chrome.storage.local.get(['results'], async items => {
79 | const results = items.results || [];
80 | results.forEach(r => {
81 | if (r.id === result.id) {
82 | Object.assign(r, result);
83 | }
84 | });
85 | chrome.storage.local.set({ results }, async() => {
86 | chrome.runtime.sendMessage({ action: actions.SET_POC, resultId: result.id, pocUrl: result.pocUrl });
87 | resolve();
88 | });
89 | });
90 | });
91 | }
92 |
93 | getPocUrls({ url, source: { label } }) {
94 | const urls = [];
95 |
96 | const prefixes = [`javascript://alert(${KEYWORD})//`, `//katagaitai.net/${KEYWORD}/`];
97 | const suffixes = [`'-alert(${KEYWORD})-'`, `"-alert(${KEYWORD})-"`, `-alert(${KEYWORD})-`, `'">
`];
98 |
99 | if (label.includes('referrer')) {
100 | for (const suffix of suffixes) {
101 | urls.push(`https://katagaitai.net/302.php?src=${encodeURIComponent(url)}&${suffix}`);
102 | }
103 | } else {
104 | const o = new URL(url);
105 | let query = o.search.slice(1);
106 | if (!query) {
107 | o.search = 'dummy=dummy';
108 | }
109 | query = o.search.slice(1);
110 | if (query) {
111 | const namedValues = query.split('&');
112 | const params = [];
113 | for (const namedValue of namedValues) {
114 | const elems = namedValue.split('=');
115 | const name = elems[0];
116 | const value = elems.slice(1).join('=');
117 | params.push({ name, value });
118 | }
119 | for (let i = 0; i < params.length; i++) {
120 | const { name, value } = params[i];
121 | for (const prefix of prefixes) {
122 | const newParams = JSON.parse(JSON.stringify(params));
123 | newParams[i] = { name, value: prefix + value };
124 | const o2 = new URL(url);
125 | o2.search = newParams.map(({ name, value }) => `${name}=${value}`).join('&');
126 | urls.push(o2.toString());
127 | }
128 | for (const suffix of suffixes) {
129 | const newParams = JSON.parse(JSON.stringify(params));
130 | newParams[i] = { name, value: value + suffix };
131 | const o2 = new URL(url);
132 | o2.search = newParams.map(({ name, value }) => `${name}=${value}`).join('&');
133 | urls.push(o2.toString());
134 | }
135 | }
136 | }
137 | let hash = o.hash.slice(1);
138 | if (!hash) {
139 | o.hash = 'dummy';
140 | }
141 | hash = o.hash.slice(1);
142 | if (hash) {
143 | for (const prefix of prefixes) {
144 | const o2 = new URL(url);
145 | o2.hash = prefix + hash;
146 | urls.push(o2.toString());
147 | }
148 | for (const suffix of suffixes) {
149 | const o2 = new URL(url);
150 | o2.hash = hash + suffix;
151 | urls.push(o2.toString());
152 | }
153 | }
154 | }
155 | console.debug('urls', urls);
156 | return urls;
157 | }
158 |
159 | checkOne(url) {
160 | return new Promise(resolve => {
161 | console.debug('checkOne', url);
162 | chrome.windows.create({ url: 'about:blank' }, async window => {
163 | const tab = window.tabs[0];
164 | if (tab) {
165 | const checker = new PocChecker(window.id, tab.id, url, KEYWORD);
166 | await checker.start();
167 | const timeoutTimer = setTimeout(() => {
168 | console.debug('timeout', window.id);
169 | checker.setIdle();
170 | }, 5000);
171 | const checkTimer = setInterval(async() => {
172 | if (checker.isIdle()) {
173 | console.debug('idle', window.id);
174 | await checker.stop();
175 | clearTimeout(timeoutTimer);
176 | clearInterval(checkTimer);
177 | console.debug('checkOne done', url, checker.detect);
178 | resolve(checker.detect);
179 | }
180 | }, 100);
181 | }
182 | });
183 | });
184 | }
185 |
186 | getResult(resultId) {
187 | return new Promise(resolve => {
188 | chrome.storage.local.get('results', async items => {
189 | const results = items.results || [];
190 | resolve(results.find(r => r.id === Number(resultId)));
191 | });
192 | });
193 | }
194 |
195 | isRunning() {
196 | return this.interceptor.isRunning;
197 | }
198 |
199 | async start() {
200 | await this.interceptor.start();
201 | chrome.browserAction.setIcon({ path: './images/icon-green.png' });
202 | chrome.browserAction.setBadgeBackgroundColor({ color: '#FF0000' });
203 | await this.setCountBadge();
204 | console.debug('start');
205 | }
206 |
207 | async stop() {
208 | await this.interceptor.stop();
209 | chrome.browserAction.setIcon({ path: './images/icon-black.png' });
210 | chrome.browserAction.setBadgeBackgroundColor({ color: '#45C8F1' });
211 | await this.setCountBadge();
212 | console.debug('stop');
213 | }
214 |
215 | setCountBadge() {
216 | return new Promise(resolve => {
217 | chrome.storage.local.get('results', items => {
218 | const results = items.results || [];
219 | // console.debug('results', results);
220 | const count = Object.keys(results).length;
221 | if (count > 0) {
222 | chrome.browserAction.setBadgeText({ text: String(count) });
223 | } else {
224 | chrome.browserAction.setBadgeText({ text: this.isRunning() ? 'ON' : '' });
225 | }
226 | resolve();
227 | });
228 | });
229 | }
230 |
231 | remove({ resultId }) {
232 | return new Promise(resolve => {
233 | chrome.storage.local.get('results', async items => {
234 | let results = items.results || [];
235 | results = results.filter(r => r.id !== Number(resultId));
236 | chrome.storage.local.set({ results }, async() => {
237 | console.debug('remove', resultId);
238 | await this.setCountBadge();
239 | resolve();
240 | });
241 | });
242 | });
243 | }
244 |
245 | removeAll() {
246 | return new Promise(resolve => {
247 | chrome.storage.local.remove('results', async() => {
248 | console.debug('removeAll');
249 | await this.setCountBadge();
250 | resolve();
251 | });
252 | });
253 | }
254 |
255 | addAll({ tabId, results }) {
256 | return new Promise(resolve => {
257 | chrome.storage.local.get(['results', 'nextId'], async items => {
258 | const oldResults = items.results || [];
259 | let nextId = items.nextId || 1;
260 | for (const result of results) {
261 | result.id = nextId++;
262 | await this.setOriginalStacktrace({ tabId, result });
263 | }
264 |
265 | // remove same results
266 | function toJSON(r) {
267 | return JSON.stringify({
268 | url: r.url,
269 | source: {
270 | label: r.source.label,
271 | stacktrace: r.source.stacktrace.map(t => ({ url: t.url, line: t.line, column: t.column }))
272 | },
273 | sink: {
274 | label: r.sink.label,
275 | stacktrace: r.sink.stacktrace.map(t => ({ url: t.url, line: t.line, column: t.column }))
276 | },
277 | });
278 | }
279 |
280 | const oldJsons = oldResults.map(r => toJSON(r));
281 | console.debug('oldResults', oldResults);
282 | const diffResults = results.filter(r => !oldJsons.includes(toJSON(r)));
283 | console.debug('diffResults', diffResults);
284 | const newResults = [...oldResults, ...diffResults];
285 | chrome.storage.local.set({ results: newResults, nextId }, async() => {
286 | console.debug('newResults', newResults, 'nextId', nextId);
287 | await this.setCountBadge();
288 | resolve();
289 | });
290 | });
291 | });
292 | }
293 |
294 | setOriginalStacktrace({ tabId, result }) {
295 | return new Promise(async resolve => {
296 | await this.resolveStackTrace(result.source.stacktrace, tabId);
297 | await this.resolveStackTrace(result.sink.stacktrace, tabId);
298 | resolve();
299 | });
300 | }
301 |
302 | async resolveStackTrace(stacktrace, tabId) {
303 | const sourceMaps = this.interceptor.sourceMaps;
304 | const bodyMaps = this.interceptor.bodyMaps;
305 | for (let i = 0; i < stacktrace.length; i++) {
306 | const trace = stacktrace[i];
307 | // console.debug('trace', trace);
308 | const { url, line, column } = trace;
309 | const body = bodyMaps[JSON.stringify({ tabId, url })];
310 | if (body) {
311 | const offset = body.match(new RegExp(`^([^\n]*\n){${line - 1}}[^\n]{${column - 1}}`))[0].length;
312 | if (offset) {
313 | for (const key of Object.keys(sourceMaps)) {
314 | const sourceMapMeta = JSON.parse(key);
315 | if (sourceMapMeta.tabId === tabId && sourceMapMeta.url === url && sourceMapMeta.start <= offset && offset < sourceMapMeta.end) {
316 | const map = sourceMaps[key];
317 | // console.debug('resolveStackTrace', key, map);
318 | stacktrace[i] = await this.getOriginalTrace(trace, map, body.slice(0, sourceMapMeta.start).split('\n').length);
319 | break;
320 | }
321 | }
322 | } else {
323 | console.debug('no offset', { trace, body });
324 | stacktrace[i] = { url, line: 0, column: 0, code: 'unknown' };
325 | }
326 | } else {
327 | console.debug('no body', { trace });
328 | stacktrace[i] = { url, line: 0, column: 0, code: 'unknown' };
329 | }
330 | }
331 | }
332 |
333 | async getOriginalTrace(trace, map, startLine) {
334 | const { url, line, column } = trace;
335 | const consumer = await new sourceMap.SourceMapConsumer(map);
336 | try {
337 | // column start from 1
338 | // Position.column start from 0
339 | const pos = consumer.originalPositionFor({ line: line - startLine + 1, column: column - 1 });
340 | // console.debug('pos', pos);
341 | let code = map.sourcesContent[0].split('\n')[pos.line - 1].slice(pos.column);
342 | if (code.length > 255) {
343 | code = code.slice(0, 255) + '...';
344 | }
345 | return { url, line: pos.line + startLine - 1, column: pos.column + 1, code };
346 | } finally {
347 | consumer.destroy();
348 | }
349 | }
350 | }
351 |
352 | //mock
353 | // import Result from '../models/result';
354 | // import Source from "../models/source";
355 | // import Sink from "../models/sink";
356 | // const urls = [
357 | // 'http://scan.example.com:3333/shop?keyword=aaa',
358 | // 'http://scan.example.com:3333/shop?keyword=aaa',
359 | // 'http://scan.example.com:3333/shop?keyword=aaa',
360 | // 'http://scan.example.com:3333/shop?keyword=aaa',
361 | // ];
362 | // const results = [];
363 | // for (let i = 0; i < urls.length; i++) {
364 | // const result = new Result({
365 | // id: i + 1,
366 | // url: urls[i],
367 | // source: new Source({
368 | // label: 'window.location.href',
369 | // stacktrace: ['http://scan.example.com:3333/shop?keyword=aaa:490:253', 'http://scan.example.com:3333/shop?keyword=aaa:409:1']
370 | // }),
371 | // sink: new Sink({
372 | // label: 'Element.innerHTML',
373 | // stacktrace: ['http://scan.example.com:3333/shop?keyword=aaa:490:5', 'http://scan.example.com:3333/shop?keyword=aaa:409:1']
374 | // })
375 | // });
376 | // results.push(result);
377 | // }
378 | // chrome.storage.local.set({ results, nextId: 5 }, () => {
379 | // console.debug('mock added');
380 | // });
381 |
382 | (async() => {
383 | const bg = new Background();
384 | await bg.setCountBadge();
385 | window.__dombasedxssfinder_background = bg;
386 | })();
387 |
--------------------------------------------------------------------------------
/src/background/preload.js:
--------------------------------------------------------------------------------
1 | if (!window.__dombasedxssfinder_preload && (location.href.startsWith('http://') || location.href.startsWith('https://'))) {
2 | window.__dombasedxssfinder_preload = true;
3 |
4 | (function() {
5 | ///////////////////////////////////////////////
6 | // String.prototype
7 | ///////////////////////////////////////////////
8 |
9 | const stringPrototypeAnchor = String.prototype.anchor;
10 | String.prototype.anchor = function() {
11 | if (__is_dombasedxssfinder_string(this)) {
12 | const str = stringPrototypeAnchor.apply(this.toString(), arguments);
13 | return new __dombasedxssfinder_String(str, this);
14 | }
15 | return stringPrototypeAnchor.apply(this, arguments);
16 | };
17 |
18 | const stringPrototypeBig = String.prototype.big;
19 | String.prototype.big = function() {
20 | if (__is_dombasedxssfinder_string(this)) {
21 | const str = stringPrototypeBig.apply(this.toString(), arguments);
22 | return new __dombasedxssfinder_String(str, this);
23 | }
24 | return stringPrototypeBig.apply(this, arguments);
25 | };
26 |
27 | const stringPrototypeBlink = String.prototype.blink;
28 | String.prototype.blink = function() {
29 | if (__is_dombasedxssfinder_string(this)) {
30 | const str = stringPrototypeBlink.apply(this.toString(), arguments);
31 | return new __dombasedxssfinder_String(str, this);
32 | }
33 | return stringPrototypeBlink.apply(this, arguments);
34 | };
35 |
36 | const stringPrototypeBold = String.prototype.bold;
37 | String.prototype.bold = function() {
38 | if (__is_dombasedxssfinder_string(this)) {
39 | const str = stringPrototypeBold.apply(this.toString(), arguments);
40 | return new __dombasedxssfinder_String(str, this);
41 | }
42 | return stringPrototypeBold.apply(this, arguments);
43 | };
44 |
45 | const stringPrototypeCharAt = String.prototype.charAt;
46 | String.prototype.charAt = function() {
47 | if (__is_dombasedxssfinder_string(this)) {
48 | const str = stringPrototypeCharAt.apply(this.toString(), arguments);
49 | return new __dombasedxssfinder_String(str, this);
50 | }
51 | return stringPrototypeCharAt.apply(this, arguments);
52 | };
53 |
54 | const stringPrototypeCharCodeAt = String.prototype.charCodeAt;
55 | String.prototype.charCodeAt = function() {
56 | return stringPrototypeCharCodeAt.apply(this.toString(), arguments);
57 | };
58 |
59 | const stringPrototypeCodePointAt = String.prototype.codePointAt;
60 | String.prototype.codePointAt = function() {
61 | return stringPrototypeCodePointAt.apply(this.toString(), arguments);
62 | };
63 |
64 | const stringPrototypeConcat = String.prototype.concat;
65 | String.prototype.concat = function() {
66 | const sources = [];
67 | for (let i = 0; i < arguments.length; i++) {
68 | arguments[i] = __convert_to_dombasedxssfinder_string_if_location(arguments[i]);
69 | if (__is_dombasedxssfinder_string(arguments[i])) {
70 | arguments[i].sources.forEach(e => sources.push(e));
71 | }
72 | }
73 | if (__is_dombasedxssfinder_string(this)) {
74 | this.sources.forEach(e => sources.push(e));
75 | }
76 | if (sources.size > 0) {
77 | const str = stringPrototypeConcat.apply(this.toString(), arguments);
78 | return new __dombasedxssfinder_String(str, { sources });
79 | }
80 | return stringPrototypeConcat.apply(this, arguments);
81 | };
82 |
83 | const stringPrototypeEndsWith = String.prototype.endsWith;
84 | String.prototype.endsWith = function() {
85 | return stringPrototypeEndsWith.apply(this.toString(), arguments);
86 | };
87 |
88 | const stringPrototypeFixed = String.prototype.fixed;
89 | String.prototype.fixed = function() {
90 | if (__is_dombasedxssfinder_string(this)) {
91 | const str = stringPrototypeFixed.apply(this.toString(), arguments);
92 | return new __dombasedxssfinder_String(str, this);
93 | }
94 | return stringPrototypeFixed.apply(this, arguments);
95 | };
96 |
97 | const stringPrototypeFontcolor = String.prototype.fontcolor;
98 | String.prototype.fontcolor = function() {
99 | if (__is_dombasedxssfinder_string(this)) {
100 | const str = stringPrototypeFontcolor.apply(this.toString(), arguments);
101 | return new __dombasedxssfinder_String(str, this);
102 | }
103 | return stringPrototypeFontcolor.apply(this, arguments);
104 | };
105 |
106 | const stringPrototypeFontsize = String.prototype.fontsize;
107 | String.prototype.fontsize = function() {
108 | if (__is_dombasedxssfinder_string(this)) {
109 | const str = stringPrototypeFontsize.apply(this.toString(), arguments);
110 | return new __dombasedxssfinder_String(str, this);
111 | }
112 | return stringPrototypeFontsize.apply(this, arguments);
113 | };
114 |
115 | const stringPrototypeIncludes = String.prototype.includes;
116 | String.prototype.includes = function() {
117 | return stringPrototypeIncludes.apply(this.toString(), arguments);
118 | };
119 |
120 | const stringPrototypeIndexOf = String.prototype.indexOf;
121 | String.prototype.indexOf = function() {
122 | return stringPrototypeIndexOf.apply(this.toString(), arguments);
123 | };
124 |
125 | const stringPrototypeItalics = String.prototype.italics;
126 | String.prototype.italics = function() {
127 | if (__is_dombasedxssfinder_string(this)) {
128 | const str = stringPrototypeItalics.apply(this.toString(), arguments);
129 | return new __dombasedxssfinder_String(str, this);
130 | }
131 | return stringPrototypeItalics.apply(this, arguments);
132 | };
133 |
134 | const stringPrototypeLastIndexOf = String.prototype.lastIndexOf;
135 | String.prototype.lastIndexOf = function() {
136 | return stringPrototypeLastIndexOf.apply(this.toString(), arguments);
137 | };
138 |
139 | const stringPrototypeLink = String.prototype.link;
140 | String.prototype.link = function() {
141 | if (__is_dombasedxssfinder_string(this)) {
142 | const str = stringPrototypeLink.apply(this.toString(), arguments);
143 | return new __dombasedxssfinder_String(str, this);
144 | }
145 | return stringPrototypeLink.apply(this, arguments);
146 | };
147 |
148 | const stringPrototypeLocaleCompare = String.prototype.localeCompare;
149 | String.prototype.localeCompare = function() {
150 | return stringPrototypeLocaleCompare.apply(this.toString(), arguments);
151 | };
152 |
153 | const stringPrototypeMatch = String.prototype.match;
154 | // TODO propagate taints of the regexp argument
155 | String.prototype.match = function() {
156 | if (__is_dombasedxssfinder_string(this)) {
157 | const array = stringPrototypeMatch.apply(this.toString(), arguments);
158 | if (array === null) {
159 | return null;
160 | }
161 | for (let i = 0; i < array.length; i++) {
162 | array[i] = new __dombasedxssfinder_String(array[i], this);
163 | }
164 | return array;
165 | }
166 | return stringPrototypeMatch.apply(this, arguments);
167 | };
168 |
169 | const stringPrototypeMatchAll = String.prototype.matchAll;
170 | // TODO propagate taints of the regexp argument
171 | String.prototype.matchAll = function() {
172 | if (__is_dombasedxssfinder_string(this)) {
173 | const iterator = stringPrototypeMatchAll.apply(this.toString(), arguments);
174 | return function* () {
175 | for (const array of iterator) {
176 | for (let i = 0; i < array.length; i++) {
177 | array[i] = new __dombasedxssfinder_String(array[i], this);
178 | }
179 | yield array;
180 | }
181 | };
182 | }
183 | return stringPrototypeMatchAll.apply(this, arguments);
184 | };
185 |
186 | const stringPrototypeNormalize = String.prototype.normalize;
187 | String.prototype.normalize = function() {
188 | if (__is_dombasedxssfinder_string(this)) {
189 | const str = stringPrototypeNormalize.apply(this.toString(), arguments);
190 | return new __dombasedxssfinder_String(str, this);
191 | }
192 | return stringPrototypeNormalize.apply(this, arguments);
193 | };
194 |
195 | const stringPrototypePadEnd = String.prototype.padEnd;
196 | String.prototype.padEnd = function() {
197 | const sources = [];
198 | arguments[1] = __convert_to_dombasedxssfinder_string_if_location(arguments[1]);
199 | if (__is_dombasedxssfinder_string(arguments[1])) {
200 | arguments[1].sources.forEach(e => sources.push(e));
201 | }
202 | if (__is_dombasedxssfinder_string(this)) {
203 | this.sources.forEach(e => sources.push(e));
204 | }
205 | if (sources.size > 0) {
206 | const str = stringPrototypePadEnd.apply(this.toString(), arguments);
207 | return new __dombasedxssfinder_String(str, { sources });
208 | }
209 | return stringPrototypePadEnd.apply(this, arguments);
210 | };
211 |
212 | const stringPrototypePadStart = String.prototype.padStart;
213 | String.prototype.padStart = function() {
214 | const sources = [];
215 | arguments[1] = __convert_to_dombasedxssfinder_string_if_location(arguments[1]);
216 | if (__is_dombasedxssfinder_string(arguments[1])) {
217 | arguments[1].sources.forEach(e => sources.push(e));
218 | }
219 | if (__is_dombasedxssfinder_string(this)) {
220 | this.sources.forEach(e => sources.push(e));
221 | }
222 | if (sources.size > 0) {
223 | const str = stringPrototypePadStart.apply(this.toString(), arguments);
224 | return new __dombasedxssfinder_String(str, { sources });
225 | }
226 | return stringPrototypePadStart.apply(this, arguments);
227 | };
228 |
229 | const stringPrototypeRepeat = String.prototype.repeat;
230 | String.prototype.repeat = function() {
231 | if (__is_dombasedxssfinder_string(this)) {
232 | const str = stringPrototypeRepeat.apply(this.toString(), arguments);
233 | return new __dombasedxssfinder_String(str, this);
234 | }
235 | return stringPrototypeRepeat.apply(this, arguments);
236 | };
237 |
238 | const stringPrototypeReplace = String.prototype.replace;
239 | String.prototype.replace = function() {
240 | const sources = [];
241 | arguments[1] = __convert_to_dombasedxssfinder_string_if_location(arguments[1]);
242 | if (__is_dombasedxssfinder_string(arguments[1])) {
243 | arguments[1].sources.forEach(e => sources.push(e));
244 | }
245 | if (__is_dombasedxssfinder_string(this)) {
246 | this.sources.forEach(e => sources.push(e));
247 | }
248 | if (sources.size > 0) {
249 | const str = stringPrototypeReplace.apply(this.toString(), arguments);
250 | return new __dombasedxssfinder_String(str, { sources });
251 | }
252 | return stringPrototypeReplace.apply(this, arguments);
253 | };
254 |
255 | const stringPrototypeSearch = String.prototype.search;
256 | String.prototype.search = function() {
257 | return stringPrototypeSearch.apply(this.toString(), arguments);
258 | };
259 |
260 | const stringPrototypeSlice = String.prototype.slice;
261 | String.prototype.slice = function() {
262 | if (__is_dombasedxssfinder_string(this)) {
263 | const str = stringPrototypeSlice.apply(this.toString(), arguments);
264 | return new __dombasedxssfinder_String(str, this);
265 | }
266 | return stringPrototypeSlice.apply(this, arguments);
267 | };
268 |
269 | const stringPrototypeSmall = String.prototype.small;
270 | String.prototype.small = function() {
271 | if (__is_dombasedxssfinder_string(this)) {
272 | const str = stringPrototypeSmall.apply(this.toString(), arguments);
273 | return new __dombasedxssfinder_String(str, this);
274 | }
275 | return stringPrototypeSlice.apply(this, arguments);
276 | };
277 |
278 | const stringPrototypeSplit = String.prototype.split;
279 | String.prototype.split = function() {
280 | if (__is_dombasedxssfinder_string(this)) {
281 | const array = stringPrototypeSplit.apply(this.toString(), arguments);
282 | for (let i = 0; i < array.length; i++) {
283 | array[i] = new __dombasedxssfinder_String(array[i], this);
284 | }
285 | return array;
286 | }
287 | return stringPrototypeSplit.apply(this, arguments);
288 | };
289 |
290 | const stringPrototypeStartsWith = String.prototype.startsWith;
291 | String.prototype.startsWith = function() {
292 | return stringPrototypeStartsWith.apply(this.toString(), arguments);
293 | };
294 |
295 | const stringPrototypeStrike = String.prototype.strike;
296 | String.prototype.strike = function() {
297 | if (__is_dombasedxssfinder_string(this)) {
298 | const str = stringPrototypeStrike.apply(this.toString(), arguments);
299 | return new __dombasedxssfinder_String(str, this);
300 | }
301 | return stringPrototypeStrike.apply(this, arguments);
302 | };
303 |
304 | const stringPrototypeSub = String.prototype.sub;
305 | String.prototype.sub = function() {
306 | if (__is_dombasedxssfinder_string(this)) {
307 | const str = stringPrototypeSub.apply(this.toString(), arguments);
308 | return new __dombasedxssfinder_String(str, this);
309 | }
310 | return stringPrototypeSub.apply(this, arguments);
311 | };
312 |
313 | const stringPrototypeSubstr = String.prototype.substr;
314 | String.prototype.substr = function() {
315 | if (__is_dombasedxssfinder_string(this)) {
316 | const str = stringPrototypeSubstr.apply(this.toString(), arguments);
317 | return new __dombasedxssfinder_String(str, this);
318 | }
319 | return stringPrototypeSubstr.apply(this, arguments);
320 | };
321 |
322 | const stringPrototypeSubstring = String.prototype.substring;
323 | String.prototype.substring = function() {
324 | if (__is_dombasedxssfinder_string(this)) {
325 | const str = stringPrototypeSubstring.apply(this.toString(), arguments);
326 | return new __dombasedxssfinder_String(str, this);
327 | }
328 | return stringPrototypeSubstring.apply(this, arguments);
329 | };
330 |
331 | const stringPrototypeSup = String.prototype.sup;
332 | String.prototype.sup = function() {
333 | if (__is_dombasedxssfinder_string(this)) {
334 | const str = stringPrototypeSup.apply(this.toString(), arguments);
335 | return new __dombasedxssfinder_String(str, this);
336 | }
337 | return stringPrototypeSup.apply(this, arguments);
338 | };
339 |
340 | const stringPrototypeToLocaleLowerCase = String.prototype.toLocaleLowerCase;
341 | String.prototype.toLocaleLowerCase = function() {
342 | if (__is_dombasedxssfinder_string(this)) {
343 | const str = stringPrototypeToLocaleLowerCase.apply(this.toString(), arguments);
344 | return new __dombasedxssfinder_String(str, this);
345 | }
346 | return stringPrototypeToLocaleLowerCase.apply(this, arguments);
347 | };
348 |
349 | const stringPrototypeToLocaleUpperCase = String.prototype.toLocaleUpperCase;
350 | String.prototype.toLocaleUpperCase = function() {
351 | if (__is_dombasedxssfinder_string(this)) {
352 | const str = stringPrototypeToLocaleUpperCase.apply(this.toString(), arguments);
353 | return new __dombasedxssfinder_String(str, this);
354 | }
355 | return stringPrototypeToLocaleUpperCase.apply(this, arguments);
356 | };
357 |
358 | const stringPrototypeToLowerCase = String.prototype.toLowerCase;
359 | String.prototype.toLowerCase = function() {
360 | if (__is_dombasedxssfinder_string(this)) {
361 | const str = stringPrototypeToLowerCase.apply(this.toString(), arguments);
362 | return new __dombasedxssfinder_String(str, this);
363 | }
364 | return stringPrototypeToLowerCase.apply(this, arguments);
365 | };
366 |
367 | // skip String.prototype.toString, which is overwritten in __dombasedxssfinder_String
368 |
369 | const stringPrototypeToUpperCase = String.prototype.toUpperCase;
370 | String.prototype.toUpperCase = function() {
371 | if (__is_dombasedxssfinder_string(this)) {
372 | const str = stringPrototypeToUpperCase.apply(this.toString(), arguments);
373 | return new __dombasedxssfinder_String(str, this);
374 | }
375 | return stringPrototypeToUpperCase.apply(this, arguments);
376 | };
377 |
378 | const stringPrototypeTrim = String.prototype.trim;
379 | String.prototype.trim = function() {
380 | if (__is_dombasedxssfinder_string(this)) {
381 | const str = stringPrototypeTrim.apply(this.toString(), arguments);
382 | return new __dombasedxssfinder_String(str, this);
383 | }
384 | return stringPrototypeTrim.apply(this, arguments);
385 | };
386 |
387 | const stringPrototypeTrimEnd = String.prototype.trimEnd;
388 | String.prototype.trimEnd = function() {
389 | if (__is_dombasedxssfinder_string(this)) {
390 | const str = stringPrototypeTrimEnd.apply(this.toString(), arguments);
391 | return new __dombasedxssfinder_String(str, this);
392 | }
393 | return stringPrototypeTrimEnd.apply(this, arguments);
394 | };
395 |
396 | const stringPrototypeTrimStart = String.prototype.trimStart;
397 | String.prototype.trimStart = function() {
398 | if (__is_dombasedxssfinder_string(this)) {
399 | const str = stringPrototypeTrimStart.apply(this.toString(), arguments);
400 | return new __dombasedxssfinder_String(str, this);
401 | }
402 | return stringPrototypeTrimStart.apply(this, arguments);
403 | };
404 |
405 | // skip String.prototype.valueOf, which is overwritten in __dombasedxssfinder_String
406 |
407 | ///////////////////////////////////////////////
408 | // RegExp.prototype
409 | ///////////////////////////////////////////////
410 |
411 | const regExpPrototypeExec = RegExp.prototype.exec;
412 | RegExp.prototype.exec = function() {
413 | const array = regExpPrototypeExec.apply(this, arguments);
414 | if (array !== null && __is_dombasedxssfinder_string(arguments[0])) {
415 | for (let i = 0; i < array.length; i++) {
416 | array[i] = new __dombasedxssfinder_String(array[i], arguments[0]);
417 | }
418 | }
419 | return array;
420 | };
421 |
422 | ///////////////////////////////////////////////
423 | // Range.prototype
424 | ///////////////////////////////////////////////
425 | const rangeCreateContextualFragment = Range.prototype.createContextualFragment;
426 | Range.prototype.createContextualFragment = function(fragment) {
427 | if (__is_dombasedxssfinder_string_html(fragment)) {
428 | __dombasedxssfinder_vulns_push(fragment.sources, 'Range.prototype.createContextualFragment()');
429 | }
430 | return rangeCreateContextualFragment.apply(this, arguments);
431 | };
432 |
433 | ///////////////////////////////////////////////
434 | // document
435 | ///////////////////////////////////////////////
436 |
437 | const documentWrite = document.write;
438 | document.write = function(...text) {
439 | for (let i = 0; i < text.length; i++) {
440 | if (__is_dombasedxssfinder_string_html(text[i])) {
441 | __dombasedxssfinder_vulns_push(text[i].sources, 'document.write()');
442 | }
443 | }
444 | return documentWrite.apply(this, arguments);
445 | };
446 |
447 | const documentWriteln = document.writeln;
448 | document.writeln = function(...text) {
449 | for (let i = 0; i < text.length; i++) {
450 | if (__is_dombasedxssfinder_string_html(text[i])) {
451 | __dombasedxssfinder_vulns_push(text[i].sources, 'document.writeln()');
452 | }
453 | }
454 | return documentWriteln.apply(this, arguments);
455 | };
456 |
457 | ///////////////////////////////////////////////
458 | // global functions
459 | ///////////////////////////////////////////////
460 |
461 | const _decodeURI = decodeURI;
462 | decodeURI = function(encodedURI) {
463 | encodedURI = __convert_to_dombasedxssfinder_string_if_location(encodedURI);
464 | if (__is_dombasedxssfinder_string(encodedURI)) {
465 | const str = _decodeURI.apply(this, [encodedURI.toString()]);
466 | const newStr = new __dombasedxssfinder_String(str, encodedURI);
467 | return newStr;
468 | }
469 | return _decodeURI.apply(this, arguments);
470 | };
471 |
472 | const _encodeURI = encodeURI;
473 | encodeURI = function(URI) {
474 | URI = __convert_to_dombasedxssfinder_string_if_location(URI);
475 | if (__is_dombasedxssfinder_string(URI)) {
476 | const str = _encodeURI.apply(this, [URI.toString()]);
477 | const newStr = new __dombasedxssfinder_String(str, URI);
478 | return newStr;
479 | }
480 | return _encodeURI.apply(this, arguments);
481 | };
482 |
483 | const _decodeURIComponent = decodeURIComponent;
484 | decodeURIComponent = function(encodedURI) {
485 | encodedURI = __convert_to_dombasedxssfinder_string_if_location(encodedURI);
486 | if (__is_dombasedxssfinder_string(encodedURI)) {
487 | const str = _decodeURIComponent.apply(this, [encodedURI.toString()]);
488 | const newStr = new __dombasedxssfinder_String(str, encodedURI);
489 | return newStr;
490 | }
491 | return _decodeURIComponent.apply(this, arguments);
492 | };
493 |
494 | const _encodeURIComponent = encodeURIComponent;
495 | encodeURIComponent = function(URI) {
496 | URI = __convert_to_dombasedxssfinder_string_if_location(URI);
497 | if (__is_dombasedxssfinder_string(URI)) {
498 | const str = _encodeURIComponent.apply(this, [URI.toString()]);
499 | const newStr = new __dombasedxssfinder_String(str, URI);
500 | return newStr;
501 | }
502 | return _encodeURIComponent.apply(this, arguments);
503 | };
504 |
505 | const _unescape = unescape;
506 | unescape = function(escapedString) {
507 | escapedString = __convert_to_dombasedxssfinder_string_if_location(escapedString);
508 | if (__is_dombasedxssfinder_string(escapedString)) {
509 | const str = _unescape.apply(this, [escapedString.toString()]);
510 | const newStr = new __dombasedxssfinder_String(str, escapedString);
511 | return newStr;
512 | }
513 | return _unescape.apply(this, arguments);
514 | };
515 |
516 | const _escape = escape;
517 | escape = function(string) {
518 | string = __convert_to_dombasedxssfinder_string_if_location(string);
519 | if (__is_dombasedxssfinder_string(string)) {
520 | const str = _escape.apply(this, [string.toString()]);
521 | const newStr = new __dombasedxssfinder_String(str, string);
522 | return newStr;
523 | }
524 | return _escape.apply(this, arguments);
525 | };
526 |
527 | const _eval = eval;
528 | eval = function(x) {
529 | if (__is_dombasedxssfinder_string_script(x)) {
530 | __dombasedxssfinder_vulns_push(x.sources, 'eval()');
531 | // eval requires toString()
532 | return _eval.apply(this, [x.toString()]);
533 | }
534 | return _eval.apply(this, arguments);
535 | };
536 |
537 | const _setInterval = setInterval;
538 | setInterval = function(handler) {
539 | if (__is_dombasedxssfinder_string_script(handler)) {
540 | __dombasedxssfinder_vulns_push(handler.sources, 'setTimeout()');
541 | }
542 | return _setInterval.apply(this, arguments);
543 | };
544 |
545 | const _setTimeout = setTimeout;
546 | setTimeout = function(handler) {
547 | if (__is_dombasedxssfinder_string_script(handler)) {
548 | __dombasedxssfinder_vulns_push(handler.sources, 'setTimeout()');
549 | }
550 | return _setTimeout.apply(this, arguments);
551 | };
552 |
553 | const _postMessage = postMessage;
554 | postMessage = function(message) {
555 | if (__is_dombasedxssfinder_string(message)) {
556 | arguments[0] = message.toString();
557 | }
558 | return _postMessage.apply(this, arguments);
559 | };
560 | })();
561 |
562 | const __dombasedxssfinder_String = function(str, parent) {
563 | this.str = '' + str;
564 | this.sources = [];
565 | parent.sources.forEach(e => this.sources.push(e));
566 |
567 | this.valueOf = function() {
568 | return this;
569 | };
570 |
571 | this.toString = function() {
572 | return this.str;
573 | };
574 |
575 | // str.length
576 | Object.defineProperty(this, 'length', {
577 | set: () => null,
578 | get: () => this.str.length
579 | });
580 |
581 | // str[0]
582 | for (let i = 0; i < this.str.length; i++) {
583 | Object.defineProperty(this, i, {
584 | set: () => null,
585 | get: () => new __dombasedxssfinder_String(this.str[i], this)
586 | });
587 | }
588 |
589 | Object.defineProperty(this, '__dombasedxssfinder_string', {
590 | set: () => null,
591 | get: () => true
592 | });
593 | };
594 | __dombasedxssfinder_String.prototype = String.prototype;
595 |
596 | function __dombasedxssfinder_plus(left, right) {
597 | left = __convert_to_dombasedxssfinder_string_if_location(left);
598 | right = __convert_to_dombasedxssfinder_string_if_location(right);
599 | if (__is_dombasedxssfinder_string(left) || __is_dombasedxssfinder_string(right)) {
600 | const sources = [];
601 | if (__is_dombasedxssfinder_string(left)) {
602 | left.sources.forEach(e => sources.push(e));
603 | }
604 | if (__is_dombasedxssfinder_string(right)) {
605 | right.sources.forEach(e => sources.push(e));
606 | }
607 | return new __dombasedxssfinder_String('' + left + right, { sources });
608 | }
609 | try {
610 | return left + right;
611 | } catch (e) {
612 | return left.toString() + right.toString();
613 | }
614 | }
615 |
616 | function __dombasedxssfinder_get(object, key) {
617 | // if (object === null || object === undefined) {
618 | // console.trace({object, key});
619 | // }
620 | if (object === window.location) {
621 | if (key === 'hash') {
622 | return new __dombasedxssfinder_String(object[key], {
623 | sources: [__dombasedxssfinder_get_source('window.location.hash')],
624 | });
625 | } else if (key === 'href') {
626 | return new __dombasedxssfinder_String(object[key], {
627 | sources: [__dombasedxssfinder_get_source('window.location.href')],
628 | });
629 | } else if (key === 'pathname') {
630 | return new __dombasedxssfinder_String(object[key], {
631 | sources: [__dombasedxssfinder_get_source('window.location.pathname')],
632 | });
633 | } else if (key === 'search') {
634 | return new __dombasedxssfinder_String(object[key], {
635 | sources: [__dombasedxssfinder_get_source('window.location.search')],
636 | });
637 | }
638 | } else if (object === document) {
639 | if (key === 'documentURI') {
640 | return new __dombasedxssfinder_String(object[key], {
641 | sources: [__dombasedxssfinder_get_source('document.documentURI')],
642 | });
643 | } else if (key === 'baseURI') {
644 | return new __dombasedxssfinder_String(object[key], {
645 | sources: [__dombasedxssfinder_get_source('document.baseURI')],
646 | });
647 | } else if (key === 'URL') {
648 | return new __dombasedxssfinder_String(object[key], {
649 | sources: [__dombasedxssfinder_get_source('document.URL')],
650 | });
651 | } else if (key === 'referrer' && object[key]) {
652 | return new __dombasedxssfinder_String(object[key], {
653 | sources: [__dombasedxssfinder_get_source('document.referrer')],
654 | });
655 | }
656 | }
657 | return object[key];
658 | }
659 |
660 | function __dombasedxssfinder_put(object, key, value) {
661 | // if (object === null || object === undefined) {
662 | // console.trace({object, key, value});
663 | // }
664 | if (object[key] === window.location && __is_dombasedxssfinder_string_script(value)) {
665 | // __dombasedxssfinder_vulns_push(value.sources, 'window.location');
666 | // kill navigation
667 | return;
668 | } else if (object === window.location && key === 'href' && __is_dombasedxssfinder_string_script(value) && value.toString() !== object[key]) {
669 | // __dombasedxssfinder_vulns_push(value.sources, 'window.location.href');
670 | // kill navigation
671 | return;
672 | } else if (object instanceof Element && key === 'innerHTML' && __is_dombasedxssfinder_string_html(value)) {
673 | __dombasedxssfinder_vulns_push(value.sources, 'Element.innerHTML');
674 | } else if (object instanceof Element && key === 'outerHTML' && __is_dombasedxssfinder_string_html(value)) {
675 | __dombasedxssfinder_vulns_push(value.sources, 'Element.outerHTML');
676 | } else if (object instanceof HTMLScriptElement && key === 'src' && __is_dombasedxssfinder_string_url(value)) {
677 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLScriptElement.src');
678 | } else if (object instanceof HTMLEmbedElement && key === 'src' && __is_dombasedxssfinder_string_url(value)) {
679 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLEmbedElement.src');
680 | } else if (object instanceof HTMLIFrameElement && key === 'src' && __is_dombasedxssfinder_string_script(value)) {
681 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLIFrameElement.src');
682 | } else if (object instanceof HTMLAnchorElement && key === 'href' && __is_dombasedxssfinder_string_script(value)) {
683 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLAnchorElement.href');
684 | } else if (object instanceof HTMLFormElement && key === 'action' && __is_dombasedxssfinder_string_script(value)) {
685 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLFormElement.action');
686 | } else if (object instanceof HTMLInputElement && key === 'formAction' && __is_dombasedxssfinder_string_script(value)) {
687 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLInputElement.formAction');
688 | } else if (object instanceof HTMLButtonElement && key === 'formAction' && __is_dombasedxssfinder_string_script(value)) {
689 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLButtonElement.formAction');
690 | } else if (object instanceof HTMLObjectElement && key === 'data' && __is_dombasedxssfinder_string_data_html(value)) {
691 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLObjectElement.data');
692 | } else if (object instanceof HTMLScriptElement && key === 'text' && __is_dombasedxssfinder_string_script(value)) {
693 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLScriptElement.text');
694 | } else if (object instanceof HTMLScriptElement && key === 'textContent' && __is_dombasedxssfinder_string_script(value)) {
695 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLScriptElement.textContent');
696 | } else if (object instanceof HTMLScriptElement && key === 'innerText' && __is_dombasedxssfinder_string_script(value)) {
697 | __dombasedxssfinder_vulns_push(value.sources, 'HTMLScriptElement.innerText');
698 | }
699 | return object[key] = value;
700 | }
701 |
702 |
703 | function __dombasedxssfinder_new_Function() {
704 | const f = new Function(...arguments);
705 | if (__is_dombasedxssfinder_string_script(arguments[arguments.length - 1])) {
706 | __dombasedxssfinder_vulns_push(arguments[arguments.length - 1].sources, 'new Function()');
707 | f.__dombasedxssfinder_str = arguments[arguments.length - 1];
708 | }
709 | return f;
710 | }
711 |
712 | function __dombasedxssfinder_equal(left, right) {
713 | if (__is_dombasedxssfinder_string(left)) {
714 | left = left.toString();
715 | }
716 | if (__is_dombasedxssfinder_string(right)) {
717 | right = right.toString();
718 | }
719 | return left == right;
720 | }
721 |
722 | function __dombasedxssfinder_notEqual(left, right) {
723 | if (__is_dombasedxssfinder_string(left)) {
724 | left = left.toString();
725 | }
726 | if (__is_dombasedxssfinder_string(right)) {
727 | right = right.toString();
728 | }
729 | return left != right;
730 | }
731 |
732 | function __dombasedxssfinder_strictEqual(left, right) {
733 | if (__is_dombasedxssfinder_string(left)) {
734 | left = left.toString();
735 | }
736 | if (__is_dombasedxssfinder_string(right)) {
737 | right = right.toString();
738 | }
739 | return left === right;
740 | }
741 |
742 | function __dombasedxssfinder_strictNotEqual(left, right) {
743 | if (__is_dombasedxssfinder_string(left)) {
744 | left = left.toString();
745 | }
746 | if (__is_dombasedxssfinder_string(right)) {
747 | right = right.toString();
748 | }
749 | return left !== right;
750 | }
751 |
752 | function __dombasedxssfinder_typeof(o) {
753 | if (__is_dombasedxssfinder_string(o)) {
754 | return 'string';
755 | }
756 | return typeof o;
757 | }
758 |
759 | function __is_dombasedxssfinder_string(o) {
760 | return o && o.__dombasedxssfinder_string;
761 | }
762 |
763 | function __is_dombasedxssfinder_string_html(o) {
764 | //