├── .gitignore
├── burp
├── .gitignore
├── .idea
│ ├── .name
│ ├── kotlinc.xml
│ ├── vcs.xml
│ ├── modules.xml
│ ├── misc.xml
│ ├── libraries
│ │ ├── net_portswigger_burp_extender_burp_extender_api_2_1.xml
│ │ └── KotlinJavaRuntime.xml
│ ├── artifacts
│ │ └── burp_jar.xml
│ └── workspace.xml
├── burp.iml
└── src
│ └── burp
│ └── BurpExtender.kt
├── firefox
├── .gitignore
├── src
│ ├── devtools
│ │ ├── index.html
│ │ ├── devtools.js
│ │ └── panel
│ │ │ ├── panel.css
│ │ │ ├── panel.js
│ │ │ └── panel.html
│ ├── contentScript.js
│ ├── settings
│ │ ├── settings.js
│ │ ├── settings.css
│ │ └── index.html
│ ├── background.js
│ ├── fileSelection.css
│ ├── config.js
│ ├── icon.js
│ ├── popup
│ │ ├── popup.html
│ │ ├── colors.css
│ │ ├── popup.js
│ │ └── popup.css
│ ├── fileSelection.js
│ └── features.js
├── icons
│ └── icon.svg
└── manifest.json
├── screenshots
├── burp.png
├── tabs.png
├── popup.png
├── post-dual.png
├── settings.png
└── post-single.png
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 |
--------------------------------------------------------------------------------
/burp/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 |
--------------------------------------------------------------------------------
/burp/.idea/.name:
--------------------------------------------------------------------------------
1 | PwnFox
--------------------------------------------------------------------------------
/firefox/.gitignore:
--------------------------------------------------------------------------------
1 | web-ext-artifacts/
2 | .web-extension-id
3 |
--------------------------------------------------------------------------------
/screenshots/burp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeswehack/PwnFox/HEAD/screenshots/burp.png
--------------------------------------------------------------------------------
/screenshots/tabs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeswehack/PwnFox/HEAD/screenshots/tabs.png
--------------------------------------------------------------------------------
/screenshots/popup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeswehack/PwnFox/HEAD/screenshots/popup.png
--------------------------------------------------------------------------------
/screenshots/post-dual.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeswehack/PwnFox/HEAD/screenshots/post-dual.png
--------------------------------------------------------------------------------
/screenshots/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeswehack/PwnFox/HEAD/screenshots/settings.png
--------------------------------------------------------------------------------
/screenshots/post-single.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeswehack/PwnFox/HEAD/screenshots/post-single.png
--------------------------------------------------------------------------------
/firefox/src/devtools/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/burp/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/burp/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/burp/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/firefox/src/contentScript.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | async function main(){
4 | const features = new ContentScriptFeatures(config)
5 | features.maybeStart()
6 | }
7 |
8 | /* Dont wait for the window to load, we need to start as soon as possible to inject the toolbox early */
9 | main()
10 |
11 |
12 |
--------------------------------------------------------------------------------
/burp/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/burp/.idea/libraries/net_portswigger_burp_extender_burp_extender_api_2_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/burp/burp.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/burp/.idea/libraries/KotlinJavaRuntime.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/firefox/src/settings/settings.js:
--------------------------------------------------------------------------------
1 | async function main() {
2 | const $ = i => document.getElementById(i)
3 |
4 | const configEl = [
5 | [$("burphost"), "burpProxyHost"],
6 | [$("burpport"), "burpProxyPort"],
7 | ]
8 |
9 |
10 | /* Config to form */
11 | configEl.forEach(([el, configName]) => {
12 | config.get(configName).then(v => el.value = v)
13 | })
14 |
15 | /* Form to config */
16 | document.querySelector("form").addEventListener("submit", ev => {
17 | configEl.forEach(([el, configName]) => {
18 | config.set(configName, el.value)
19 | })
20 | });
21 | newFileSelection(config, "savedToolbox", "#savedToolbox", 'toolbox')
22 | }
23 |
24 | document.addEventListener("DOMContentLoaded", main)
25 |
--------------------------------------------------------------------------------
/burp/.idea/artifacts/burp_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/out/artifacts/burp_jar
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/firefox/src/devtools/devtools.js:
--------------------------------------------------------------------------------
1 | const title = "Messages"
2 | const icon = "/icons/icon.svg"
3 | const panel = "/src/devtools/panel/panel.html"
4 |
5 |
6 | browser.devtools.panels.create(title, icon, panel).then(panel => {
7 | const port = chrome.runtime.connect({ name: `devtools-${browser.devtools.inspectedWindow.tabId}` });
8 | let messageHistory = [];
9 | let _window = null
10 |
11 | port.onMessage.addListener(function (msg) {
12 | if (_window) {
13 | _window.handleMessage(msg);
14 | } else {
15 | messageHistory.push(msg);
16 | }
17 | });
18 |
19 | panel.onShown.addListener(function (panelWindow) {
20 | panel.onShown.removeListener(this);
21 | _window = panelWindow
22 | let msg;
23 | while (msg = messageHistory.shift()) {
24 | _window.handleMessage(msg)
25 | }
26 | });
27 | })
--------------------------------------------------------------------------------
/firefox/icons/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
23 |
--------------------------------------------------------------------------------
/firefox/src/background.js:
--------------------------------------------------------------------------------
1 | /* Communication from contentScript to devtools */
2 |
3 | const Coms = new class {
4 | constructor() {
5 | this.ports = {}
6 | }
7 |
8 | connect(port) {
9 | this.ports[port.name] = port
10 | port.onDisconnect.addListener(p => {
11 | delete this.ports[p.name]
12 | })
13 | }
14 |
15 | postMessage(name, message) {
16 | if (this.ports[name])
17 | this.ports[name].postMessage(message)
18 | }
19 | }
20 |
21 | function handleMessage(message, sender) {
22 | Coms.postMessage(`devtools-${sender.tab.id}`, message)
23 | }
24 |
25 |
26 |
27 |
28 | /* */
29 |
30 | async function main() {
31 | const features = new BackgroundFeatures(config)
32 |
33 | features.maybeStart()
34 |
35 | browser.runtime.onConnect.addListener(port => Coms.connect(port))
36 | browser.runtime.onMessage.addListener(handleMessage);
37 | }
38 |
39 | window.addEventListener("load", main)
--------------------------------------------------------------------------------
/firefox/src/fileSelection.css:
--------------------------------------------------------------------------------
1 | .file-list {
2 | display: flex;
3 | flex-direction: column;
4 | justify-items: stretch;
5 | width: 100%;
6 | }
7 |
8 | .file-list-head {
9 | display: grid;
10 | grid-template-columns: 1fr repeat(4, auto);
11 | }
12 |
13 | .file-list-head input[type="file"] {
14 | display: none;
15 | }
16 |
17 | .file-list-head button {
18 | background: inherit;
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 | color: inherit;
23 | width: 50px;
24 | }
25 |
26 | .file-list-head button:hover {
27 | filter: invert(50%);
28 | }
29 |
30 | .file-list-head select {
31 | background: inherit;
32 | line-height: 1.2em;
33 | padding: 5px;
34 | color: inherit;
35 | }
36 |
37 | .file-list svg path {
38 | fill: currentColor;
39 | }
40 |
41 | .file-list-body {
42 | display: flex;
43 | justify-items: stretch;
44 | height: 100%;
45 | }
46 |
47 | .file-list-body textarea {
48 | flex-grow: 1;
49 | margin: 0;
50 | height: 100%;
51 | padding: 10px;
52 | -webkit-appearance: none;
53 |
54 | }
55 |
56 |
57 | .file-list-body textarea.changed {
58 | box-shadow: 0 0 13px 0px blue inset;
59 | }
--------------------------------------------------------------------------------
/firefox/src/settings/settings.css:
--------------------------------------------------------------------------------
1 | @media (prefers-color-scheme: dark) {
2 | body {
3 | background-color: #202023;
4 | color: white;
5 | }
6 | }
7 |
8 | body {
9 | display: flex;
10 | font-family: Helvetica, sans-serif;
11 | flex-direction: column;
12 | justify-items: stretch;
13 | align-items: flex-start;
14 | font-size: 12px;
15 | line-height: 1.2em;
16 | min-height: 100vh;
17 | }
18 |
19 | hr {
20 | width: 90%;
21 | margin-top: 15px;
22 | margin-bottom: 15px;
23 | }
24 |
25 | ul {
26 | display: flex;
27 | flex-direction: column;
28 | align-items: start;
29 | margin-bottom: 10px;
30 | list-style: none;
31 | padding-left: 0px;
32 | }
33 |
34 | ul ul {
35 | padding-left: 20px;
36 | }
37 |
38 | li.option-entry {
39 | width: 100%;
40 | display: flex;
41 | flex-direction: row;
42 | align-items: center;
43 | justify-content: flex-start;
44 | margin: 5px 0;
45 | }
46 |
47 | label {
48 | font-size: 1.2em;
49 | color: var(--in-content-primary-button-background);
50 | }
51 |
52 | input {
53 | margin-left: 5px;
54 | padding: 2px;
55 | }
56 |
57 | label::after {
58 | content: ":";
59 | }
60 |
61 | button#save {
62 | align-self: flex-end;
63 | }
64 |
65 | form {
66 | display: contents;
67 | }
68 |
69 | textarea {
70 | min-height: 300px;
71 | }
--------------------------------------------------------------------------------
/firefox/src/config.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const defaultConfig = {
4 | enabled: false,
5 | useBurpProxy: false,
6 | addContainerHeader: true,
7 | injectToolbox: false,
8 | logPostMessage: true,
9 | removeSecurityHeaders: false,
10 | burpProxyHost: '127.0.0.1',
11 | burpProxyPort: '8080',
12 | activeToolbox: null,
13 | savedToolbox: {},
14 | devToolDual: false,
15 | activeMessageFunc: "noop",
16 | savedMessageFunc: {
17 | "noop": `/*
18 | * Available parameters:
19 | * data: the message data
20 | * origin: the origin frame
21 | * destination: the destination frame
22 | *
23 | * return:
24 | * new modified message to display
25 | */
26 |
27 | return data
28 | `}
29 | }
30 |
31 |
32 | const config = {
33 | async get(key) {
34 | const r = await browser.storage.local.get(key)
35 | return r[key] ?? defaultConfig[key]
36 | },
37 | async set(key, value) {
38 | return await browser.storage.local.set({ [key]: value })
39 | },
40 | onChange(key, handler) {
41 | return browser.storage.onChanged.addListener((changes, areaName) => {
42 | if (areaName != "local") return
43 |
44 | for (const [name, { newValue }] of Object.entries(changes)) {
45 | if (name != key) continue
46 | handler(newValue)
47 | }
48 | })
49 | }
50 | }
--------------------------------------------------------------------------------
/burp/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 1619611912415
23 |
24 |
25 | 1619611912415
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/firefox/src/icon.js:
--------------------------------------------------------------------------------
1 | /* Icon */
2 |
3 | function createCanvas(width, height) {
4 | const canvas = document.createElement("canvas")
5 | canvas.width = width
6 | canvas.height = height
7 | return canvas
8 | }
9 |
10 | function loadImage(src) {
11 | return new Promise(resolve => {
12 | const img = new Image()
13 | img.onload = ev => resolve(img)
14 | img.src = src
15 | })
16 | }
17 |
18 | async function createIcon(dotColor) {
19 | const width = 48;
20 | const height = width;
21 | const canvas = createCanvas(width, height)
22 | const img = await loadImage("/icons/icon.svg")
23 | const context = canvas.getContext('2d')
24 |
25 | context.drawImage(img, 0, 0, width, height);
26 | if (dotColor !== null) {
27 | context.beginPath();
28 | context.arc(8, 40, 7, 0, 2 * Math.PI, false);
29 | context.fillStyle = dotColor;
30 | context.fill();
31 | context.lineWidth = 2;
32 | context.strokeStyle = "black";
33 | context.stroke();
34 |
35 | /* fox don't cry */
36 | if (dotColor === "#ff0000") {
37 | context.beginPath();
38 | context.ellipse(33, 28, .1, .5, 0, 0, 2 * Math.PI);
39 | context.lineWidth = 2;
40 | context.strokeStyle = "#ffffff";
41 | context.stroke();
42 | }
43 |
44 | }
45 | return [canvas, context.getImageData(0, 0, width, height)]
46 | }
47 |
--------------------------------------------------------------------------------
/firefox/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Pwnfox",
3 | "manifest_version": 2,
4 | "name": "PwnFox",
5 | "version": "1.0.2",
6 | "icons": {
7 | "48": "icons/icon.svg"
8 | },
9 | "browser_specific_settings": {
10 | "gecko": {
11 | "id": "PwnFox@bi.tk"
12 | }
13 | },
14 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';",
15 | "permissions": [
16 | "activeTab",
17 | "storage",
18 | "webRequest",
19 | "contextualIdentities",
20 | "cookies",
21 | "proxy",
22 | "theme",
23 | "webRequestBlocking",
24 | "",
25 | "tabs"
26 | ],
27 | "options_ui": {
28 | "page": "src/settings/index.html",
29 | "browser_style": true
30 | },
31 | "browser_action": {
32 | "default_popup": "src/popup/popup.html",
33 | "default_icon": {
34 | "48": "icons/icon.svg"
35 | },
36 | "default_title": "PwnFox",
37 | "browser_style": true
38 | },
39 | "background": {
40 | "scripts": [
41 | "src/icon.js",
42 | "src/config.js",
43 | "src/features.js",
44 | "src/background.js"
45 | ]
46 | },
47 | "devtools_page": "src/devtools/index.html",
48 | "content_scripts": [
49 | {
50 | "run_at": "document_start",
51 | "all_frames": true,
52 | "matches": [
53 | ""
54 | ],
55 | "js": [
56 | "src/config.js",
57 | "src/features.js",
58 | "src/contentScript.js"
59 | ]
60 | }
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/firefox/src/popup/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | PwnFox
14 |
15 |
18 |
Settings
19 |
20 |
21 |
22 |
23 | Options
24 |
25 |
28 |
31 |
34 |
37 |
38 | Toolbox
39 |
40 |
41 |
42 |
43 | New container tab
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/burp/src/burp/BurpExtender.kt:
--------------------------------------------------------------------------------
1 | package burp
2 |
3 | import java.io.PrintWriter
4 | import java.util.*
5 |
6 | class BurpExtender : IBurpExtender, IProxyListener, IExtensionStateListener {
7 |
8 | private lateinit var callbacks: IBurpExtenderCallbacks
9 | private lateinit var helpers: IExtensionHelpers
10 | private lateinit var stdout: PrintWriter
11 | private lateinit var stderr: PrintWriter
12 |
13 | override fun registerExtenderCallbacks(callbacks: IBurpExtenderCallbacks) {
14 | this.callbacks = callbacks
15 | this.helpers = callbacks.helpers
16 | this.stdout = PrintWriter(callbacks.stdout, true)
17 | this.stderr = PrintWriter(callbacks.stderr, true)
18 |
19 | callbacks.setExtensionName("PwnFox")
20 | callbacks.registerExtensionStateListener(this)
21 | callbacks.registerProxyListener(this)
22 | stdout.println("PwnFox Loaded")
23 | }
24 |
25 |
26 | override fun extensionUnloaded() {
27 | }
28 |
29 | override fun processProxyMessage(messageIsRequest: Boolean, message: IInterceptedProxyMessage?) {
30 | if (!messageIsRequest) return
31 |
32 | val messageInfo = message?.messageInfo
33 | if (messageInfo != null) {
34 |
35 | val requestInfo = helpers.analyzeRequest(messageInfo)
36 | val body = messageInfo.request.drop(requestInfo.bodyOffset).toByteArray()
37 | val (pwnFoxHeaders, cleanHeaders) = requestInfo.headers.partition {
38 | it.lowercase(Locale.getDefault()).startsWith("x-pwnfox-")
39 | }
40 |
41 | pwnFoxHeaders.forEach() {
42 | if (it.lowercase(Locale.getDefault()).startsWith(("x-pwnfox-color:"))) {
43 | val (_, color) = it.split(":", limit = 2)
44 | messageInfo.highlight = color.trim()
45 | }
46 | }
47 | messageInfo.request = helpers.buildHttpMessage(cleanHeaders, body)
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/firefox/src/popup/colors.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --magenta-50: #ff1ad9;
3 | --magenta-60: #ed00b5;
4 | --magenta-70: #b5007f;
5 | --magenta-80: #7d004f;
6 | --magenta-90: #440027;
7 | --purple-30: #c069ff;
8 | --purple-40: #ad3bff;
9 | --purple-50: #9400ff;
10 | --purple-60: #8000d7;
11 | --purple-70: #6200a4;
12 | --purple-80: #440071;
13 | --purple-90: #25003e;
14 | --blue-40: #45a1ff;
15 | --blue-50: #0a84ff;
16 | --blue-50-a30: rgba(10, 132, 255, 0.3);
17 | --blue-60: #0060df;
18 | --blue-70: #003eaa;
19 | --blue-80: #002275;
20 | --blue-90: #000f40;
21 | --teal-50: #00feff;
22 | --teal-60: #00c8d7;
23 | --teal-70: #008ea4;
24 | --teal-80: #005a71;
25 | --teal-90: #002d3e;
26 | --green-50: #30e60b;
27 | --green-60: #12bc00;
28 | --green-70: #058b00;
29 | --green-80: #006504;
30 | --green-90: #003706;
31 | --yellow-50: #ffe900;
32 | --yellow-60: #d7b600;
33 | --yellow-70: #a47f00;
34 | --yellow-80: #715100;
35 | --yellow-90: #3e2800;
36 | --red-50: #ff0039;
37 | --red-60: #d70022;
38 | --red-70: #a4000f;
39 | --red-80: #5a0002;
40 | --red-90: #3e0200;
41 | --orange-50: #ff9400;
42 | --orange-60: #d76e00;
43 | --orange-70: #a44900;
44 | --orange-80: #712b00;
45 | --orange-90: #3e1300;
46 | --grey-10: #f9f9fa;
47 | --grey-10-a10: rgba(249, 249, 250, 0.1);
48 | --grey-10-a20: rgba(249, 249, 250, 0.2);
49 | --grey-10-a40: rgba(249, 249, 250, 0.4);
50 | --grey-10-a60: rgba(249, 249, 250, 0.6);
51 | --grey-10-a80: rgba(249, 249, 250, 0.8);
52 | --grey-20: #ededf0;
53 | --grey-30: #d7d7db;
54 | --grey-40: #b1b1b3;
55 | --grey-50: #737373;
56 | --grey-60: #4a4a4f;
57 | --grey-70: #38383d;
58 | --grey-80: #2a2a2e;
59 | --grey-90: #0c0c0d;
60 | --grey-90-a05: rgba(12, 12, 13, 0.05);
61 | --grey-90-a10: rgba(12, 12, 13, 0.1);
62 | --grey-90-a20: rgba(12, 12, 13, 0.2);
63 | --grey-90-a30: rgba(12, 12, 13, 0.3);
64 | --grey-90-a40: rgba(12, 12, 13, 0.4);
65 | --grey-90-a50: rgba(12, 12, 13, 0.5);
66 | --grey-90-a60: rgba(12, 12, 13, 0.6);
67 | --grey-90-a70: rgba(12, 12, 13, 0.7);
68 | --grey-90-a80: rgba(12, 12, 13, 0.8);
69 | --grey-90-a90: rgba(12, 12, 13, 0.9);
70 | --ink-70: #363959;
71 | --ink-80: #202340;
72 | --ink-90: #0f1126;
73 | --white-100: #ffffff;
74 | }
--------------------------------------------------------------------------------
/firefox/src/popup/popup.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | /* Containers Identity */
4 | async function getOrCreateIdentity(color) {
5 | const name = `PwnFox-${color}`
6 | const icon = "fingerprint"
7 | const [identity] = await browser.contextualIdentities.query({ name })
8 | if (identity !== undefined) {
9 | return identity
10 | }
11 | return await browser.contextualIdentities.create({ name, color, icon })
12 | }
13 |
14 | async function createContainerTab(color) {
15 | const identity = await getOrCreateIdentity(color)
16 | const { cookieStoreId } = identity
17 | return browser.tabs.create({ cookieStoreId })
18 | }
19 |
20 | async function bindCheckboxToConfig(selector, config, configName) {
21 | const checkbox = document.querySelector(selector)
22 | checkbox.checked = await config.get(configName)
23 | checkbox.addEventListener("change", () => config.set(configName, checkbox.checked))
24 | }
25 |
26 |
27 |
28 | function createContainerTabButtons() {
29 | const colors = [
30 | "blue",
31 | "turquoise",
32 | "green",
33 | "yellow",
34 | "orange",
35 | "red",
36 | "pink",
37 | "purple"
38 | ]
39 | const container = document.querySelector("#identities")
40 | colors.forEach(color => {
41 | const div = document.createElement("div")
42 | div.classList.add("identity", color)
43 | div.addEventListener("click", ev => {
44 | createContainerTab(color)
45 | })
46 | container.appendChild(div)
47 | })
48 |
49 | }
50 |
51 | async function togglePwnfox(enabled) {
52 | const color = enabled ? "#00ff00" : "#ff0000"
53 | const [canvas] = await createIcon(color)
54 | const iconContainer = document.getElementById("icon")
55 | iconContainer.replaceChild(canvas, iconContainer.firstChild)
56 |
57 | const main = document.querySelector("main")
58 | if (!enabled) {
59 | main.classList.add('disabled')
60 | } else {
61 | main.classList.remove('disabled')
62 | }
63 | }
64 |
65 | async function main() {
66 |
67 | createContainerTabButtons()
68 |
69 | bindCheckboxToConfig("#option-enabled", config, "enabled")
70 | bindCheckboxToConfig("#option-useBurpProxyAll", config, "useBurpProxyAll")
71 | bindCheckboxToConfig("#option-useBurpProxyContainer", config, "useBurpProxyContainer")
72 | bindCheckboxToConfig("#option-addContainerHeader", config, "addContainerHeader")
73 | bindCheckboxToConfig("#option-removeSecurityHeaders", config, "removeSecurityHeaders")
74 | bindCheckboxToConfig("#option-injectToolbox", config, "injectToolbox")
75 |
76 | /* Hook settings link */
77 | document.querySelector("#settings").addEventListener("click", ev => {
78 | browser.runtime.openOptionsPage()
79 | })
80 |
81 | const select = document.getElementById("select-toolbox")
82 | const filenames = Object.keys(await config.get("savedToolbox"))
83 | const activeToolbox = await config.get("activeToolbox");
84 | for (const filename of filenames) {
85 | const option = document.createElement("option")
86 | option.value = filename
87 | option.selected = filename === activeToolbox
88 | option.innerText = filename
89 | select.appendChild(option)
90 | }
91 | select.addEventListener("change", () => {
92 | config.set("activeToolbox", select.value)
93 | })
94 | config.onChange('enabled', togglePwnfox, true)
95 | togglePwnfox(await config.get("enabled"))
96 | }
97 |
98 | window.addEventListener("load", main)
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/firefox/src/popup/popup.css:
--------------------------------------------------------------------------------
1 | @import '/src/popup/colors.css';
2 |
3 | @media (prefers-color-scheme: dark) {
4 | :root {
5 | --background-color: var(--grey-80);
6 | --color: var(--grey-20);
7 | --color-disabled: var(--grey-40);
8 | }
9 | }
10 |
11 | @media (prefers-color-scheme: light) {
12 | :root {
13 | --background-color: var(--grey-20);
14 | --color: var(--grey-90);
15 | --color-disabled: var(--grey-70);
16 | }
17 | }
18 |
19 | main {
20 | display: flex;
21 | flex-direction: column;
22 | }
23 |
24 | main.diasabled {
25 | color: var(--color-disabled);
26 | }
27 |
28 | body {
29 | padding: 5px 10px 10px 10px;
30 | background-color: var(--background-color);
31 | color: var(--color);
32 | width: 300px;
33 | overflow: hidden;
34 | }
35 |
36 | .option-entry {
37 | display: grid;
38 | grid-template-columns: auto 50px;
39 | }
40 |
41 | header {
42 | display: grid;
43 | grid-template-columns: 48px auto auto 5px;
44 | grid-column-gap: 10px;
45 | align-items: center;
46 | }
47 |
48 | header img {
49 | width: 48px;
50 | margin: 0 10px;
51 | }
52 |
53 | header .right{
54 | display: flex;
55 | flex-direction: column;
56 | justify-items: end;
57 | }
58 |
59 | hr {
60 | width: 80%;
61 | margin:10px auto ;
62 | border-bottom: 1px solid var(--color);
63 | }
64 |
65 | header #enable{
66 | margin-bottom: 2px;
67 | }
68 |
69 | header #enable div {
70 | justify-self: right;
71 | text-align: right;
72 | display: flex;
73 | }
74 |
75 | header h1 {
76 | margin: 10px;
77 | font-weight: lighter;
78 | }
79 |
80 |
81 | h2, h1 {
82 | font-weight: lighter;
83 | margin: 0;
84 | text-align: center;
85 | position: relative;
86 | display: flex;
87 | flex-direction: column;
88 | }
89 |
90 |
91 | h2 input , h1 input {
92 | position: absolute;
93 | float:right;
94 | top: 50%;
95 | transform: translateY(-50%);
96 | right: 0px;
97 | height: 1.8em;
98 | margin-right: 40px;
99 | }
100 |
101 |
102 | h2:not(:first-child){
103 | margin-top: 10px;
104 | }
105 |
106 | #identities {
107 | display: flex;
108 | justify-content: space-between;
109 | }
110 |
111 | .identity {
112 | display: block;
113 | --size: 25px;
114 | margin: 5px;
115 | height: var(--size);
116 | width: var(--size);
117 | border-radius: 7px;
118 | cursor: pointer;
119 | transition: .15s background-color, .1s transform;
120 | }
121 |
122 | .identity:active{
123 | transform: scale(.9);
124 |
125 | }
126 |
127 | .identity.blue {
128 | background: var(--blue-50);
129 | }
130 |
131 | .identity.blue:hover {
132 | background: var(--blue-70);
133 | }
134 |
135 | .identity.turquoise {
136 | background: var(--teal-50);
137 | }
138 |
139 | .identity.turquoise:hover {
140 | background: var(--teal-70);
141 | }
142 |
143 | .identity.green {
144 | background: var(--green-50);
145 | }
146 |
147 | .identity.green:hover {
148 | background: var(--green-70);
149 | }
150 |
151 | .identity.yellow {
152 | background: var(--yellow-50);
153 | }
154 |
155 | .identity.yellow:hover {
156 | background: var(--yellow-70);
157 | }
158 |
159 | .identity.orange {
160 | background: var(--orange-50);
161 | }
162 |
163 | .identity.orange:hover {
164 | background: var(--orange-70);
165 | }
166 |
167 | .identity.red {
168 | background: var(--red-50);
169 | }
170 |
171 | .identity.red:hover {
172 | background: var(--red-70);
173 | }
174 |
175 | .identity.pink {
176 | background: var(--magenta-50);
177 | }
178 |
179 | .identity.pink:hover {
180 | background: var(--magenta-70);
181 | }
182 |
183 | .identity.purple {
184 | background: var(--purple-50);
185 | }
186 |
187 | .identity:hover.purple {
188 | background: var(--purple-70);
189 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
PwnFox
2 |
3 | PwnFox is a Firefox/Burp extension that provide usefull tools for your security audit.
4 |
5 | If you are a chrome user you can check https://github.com/nccgroup/autochrome.
6 |
7 | - [PwnFox](#img-srcfirefoxiconsiconsvg-width30-pwnfox)
8 | - [Features](#features)
9 | - [Single click BurpProxy](#single-click-burpproxy)
10 | - [Containers Profiles](#containers-profiles)
11 | - [PostMessage Logger](#postmessage-logger)
12 | - [Toolbox](#toolbox)
13 | - [Security header remover](#security-header-remover)
14 | - [Installation](#installation)
15 | - [Build](#build)
16 | - [All](#all)
17 | - [Firefox](#firefox)
18 | - [Burp](#burp)
19 | - [Changelog](#changelog)
20 |
21 |
22 | ## Features
23 |
24 | 
25 |
26 | ### Single click BurpProxy
27 |
28 | Connect to Burp with a simple click, this will probably remove the need for other addons like foxyProxy. However if you need the extra features provided by foxyProxy you can leave this unchecked.
29 |
30 | ### Containers Profiles
31 |
32 | PwnFox give you fast access to the Firefox containers. This allow you to have multiple identities in the same browser.
33 | When PwnFox and the `Add container header` option are enabled, PwnFox will automatically add a `X-PwnFox-Color` header to hightlight the query in Burp.
34 |
35 | PwnFoxBurp will automatically highlight and strip the header, but you can also specify your own behavior with addons like logger++.
36 |
37 | 
38 | 
39 |
40 |
41 |
42 | ### PostMessage Logger
43 |
44 | PwnFox add a new message tab in you devtool. This allow you to quickly visualize all postMessage between frames.
45 |
46 | 
47 |
48 | You can also provide your own function to parse/filter the messages.
49 | You get access to 3 arguments:
50 | * data -> the message data
51 | * origin -> the window object representing the origin
52 | * destion -> the window object representing the destination
53 |
54 | You can return a string or a JSON serializable object.
55 |
56 | 
57 |
58 |
59 | ### Toolbox
60 |
61 | Inject you own javascript code on page load. The code will be loaded as soon as possible. This can used to add dangerous behavior detection, or just to add extra function to your js console.
62 |
63 | **Be carefull, the injected toolbox will run in the window context. Do not inject secret in untrusted domain.**
64 |
65 |
66 | 
67 |
68 | I will publish some of my toolbox soon (ENOTIME)
69 |
70 |
71 | ### Security header remover
72 |
73 | Sometime it's easier to work with security header disabled. You can now do it with a single button press. Don't forget to reenable them before testing your final payload.
74 |
75 | Headers stripped:
76 | * Content-Security-Policy
77 | * X-XSS-Protection
78 | * X-Frame-Options
79 | * X-Content-Type-Options
80 |
81 | ## Installation
82 |
83 |
84 | You can find the latest build here:
85 | * [https://github.com/B-i-t-K/PwnFox/releases](https://github.com/B-i-t-K/PwnFox/releases)
86 |
87 | ### Firefox
88 | - visit `about:addons` and choose install from file, then select `PwnFox-$version.xpi`
89 | - or install from
90 | [https://addons.mozilla.org/en-US/firefox/addon/pwnfox/](https://addons.mozilla.org/en-US/firefox/addon/pwnfox/)
91 |
92 | ### Burp
93 | - Go to extender and add `PwnFox-Burp.jar` as a java extension.
94 |
95 | ## Build
96 |
97 | ### Firefox
98 |
99 | ```shell
100 | cd firefox
101 | web-ext build
102 | # the zip file is available in /firefox/web-ext-artifacts/pwnfox-${version}.zip
103 | # Optional. If you want to sign you own build
104 | web-ext sign --api-key="$KEY" --api-secret="$SECRET"
105 | # the xpi file is available in /firefox/web-ext-artifacts/pwnfox-${version}.xpi
106 |
107 | ```
108 | ### Burp
109 |
110 | Open and compile with Intellij IDEA
111 |
112 | ## Changelog
113 |
114 | * v1.0.3
115 | * Fix missing highlight with burp v2021.4.2
116 | * v1.0.2
117 | * First public release
118 |
--------------------------------------------------------------------------------
/firefox/src/devtools/panel/panel.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --background-color: #232327;
3 | --text-color: #C6C6CA;
4 | }
5 |
6 | html,
7 | body {
8 | padding: 0;
9 | margin: 0;
10 | color: var(--text-color);
11 | }
12 |
13 | * {
14 | box-sizing: border-box;
15 | }
16 |
17 |
18 | main {
19 | background-color: #0C0C0D;
20 | font-family: monospace;
21 | display: grid;
22 | grid-template-columns: 1fr;
23 | grid-template-rows: auto 1fr;
24 | grid-template-areas: "toolbar""list";
25 | align-items: start;
26 | border-bottom: 2px solid black;
27 | height: 100vh;
28 | }
29 |
30 | main.dual {
31 | grid-template-columns: auto 500px;
32 | grid-template-areas: "toolbar filter""list filter";
33 | }
34 |
35 | #message-toolbar {
36 | grid-area: toolbar;
37 | display: flex;
38 | column-gap: 5px;
39 | background: #2a2a2e;
40 | padding: 5px;
41 | width: 100%;
42 | }
43 |
44 | #message-toolbar svg {
45 | padding: 4px;
46 | }
47 |
48 | #message-toolbar button,
49 | #message-toolbar svg {
50 |
51 | border: none;
52 | border-radius: 2px;
53 | white-space: nowrap;
54 | user-select: none;
55 | background-color: rgba(249, 249, 250, 0.2);
56 | color: rgb(215, 215, 219);
57 | text-align: center;
58 | }
59 |
60 | #message-toolbar button:hover,
61 | #message-toolbar svg:hover {
62 | background-color: rgba(249, 249, 250, 0.3);
63 |
64 | }
65 |
66 | #message-list {
67 | display: contents;
68 | }
69 |
70 |
71 | #message-table {
72 | grid-area: list;
73 | overflow-y: auto;
74 | overflow-x: hidden;
75 | scrollbar-width: thin;
76 | height: 100%;
77 | display: flex;
78 | flex-direction: column;
79 | vertical-align: middle;
80 | justify-content: start;
81 | counter-reset: message-id;
82 | }
83 |
84 | #message-table>.row {
85 | top: 0;
86 | position: sticky;
87 | z-index: 10;
88 | background: #2a2a2e;
89 | }
90 |
91 | .spacer {
92 | flex-grow: 1;
93 | }
94 |
95 | .row {
96 | display: grid;
97 | grid-template-columns: 40px 1fr 1fr 4fr 75px;
98 | line-height: 14px;
99 | font-size: 11px;
100 | border-bottom: 1px solid #4E4E51;
101 | background: var(--background-color);
102 | background: #2a2a2e;
103 | }
104 |
105 | .row:hover {
106 | background: #3a3a3e
107 | }
108 |
109 | .row span::after {
110 | display: block;
111 | content: "";
112 | top: -1px;
113 | bottom: -1px;
114 | right: 0px;
115 | left: -1px;
116 | position: absolute;
117 | border-right: 1px solid #4E4E51;
118 | }
119 |
120 | #message-list .row span:first-child:before {
121 | counter-increment: message-id;
122 | content: counter(message-id, decimal-leading-zero)
123 | }
124 |
125 | #message-list .row:hover {
126 | border-bottom: 1px solid #4E4E51;
127 | }
128 |
129 | #message-table>.row {
130 | border-top: 1px solid #4E4E51;
131 | font-weight: bold;
132 | }
133 |
134 |
135 | .row span {
136 | position: relative;
137 | white-space: nowrap;
138 | overflow-x: hidden;
139 |
140 | text-overflow: ellipsis;
141 | padding: 2px 4px;
142 | }
143 |
144 | #message-filter {
145 | display: none;
146 | }
147 |
148 | .dual #message-filter {
149 | grid-area: filter;
150 | display: grid;
151 | grid-template-rows: 30px auto;
152 | height: 100%;
153 | }
154 |
155 | #message-filter h2 {
156 | text-align: center;
157 | margin: 0;
158 | line-height: 30px;
159 | font-weight: lighter;
160 | }
161 |
162 | textarea {
163 | padding: 5px;
164 | height: 100%;
165 | display: block;
166 | resize: none;
167 | }
168 |
169 | .message-details-content {
170 | padding: 10px;
171 | display: grid;
172 | grid-template-columns: 90px 1fr;
173 | row-gap: 10px;
174 | column-gap: 10px;
175 | background: #0c0c0d;
176 | }
177 |
178 | .message-details-content span:nth-child(even) {
179 | white-space: pre;
180 | }
181 |
182 | summary {
183 | cursor: pointer;
184 | outline: 0;
185 | }
186 |
187 | .message-details-content span:nth-child(odd) {
188 | border-right: 1px solid #b1b1b3;
189 | }
--------------------------------------------------------------------------------
/firefox/src/settings/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/firefox/src/fileSelection.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | async function newFileSelection(config, storeName, selector, defaultName) {
5 | const el = document.querySelector(selector)
6 | const select = el.querySelector("select")
7 | const files = await config.get(storeName);
8 |
9 | const newBtn = el.querySelector(".file-list-new")
10 | const saveBtn = el.querySelector(".file-list-save")
11 | const deleteBtn = el.querySelector(".file-list-delete")
12 | const editBtn = el.querySelector(".file-list-edit")
13 | const textarea = el.querySelector("textarea")
14 | /* Utils */
15 |
16 | function createOption(filename) {
17 | const option = document.createElement("option")
18 | option.innerText = filename
19 | option.value = filename
20 | return option
21 | }
22 |
23 | function preventDuplicate(filenames, newName) {
24 | let idx = 2;
25 | let testName = newName
26 | while (testName in filenames) {
27 | testName = `${newName} (${idx})`
28 | idx += 1;
29 | }
30 | return testName
31 | }
32 | function addShadow() {
33 | if (textarea.value !== files[select.value]) {
34 | textarea.classList.add("changed")
35 | } else {
36 | textarea.classList.remove("changed")
37 | }
38 | }
39 | /* File operations */
40 |
41 | function addFile(filename, content) {
42 | select.appendChild(createOption(filename))
43 | saveFile(filename, content)
44 | textarea.disabled = select.children.length === 0
45 | select.value = filename
46 | showFile(filename)
47 | }
48 |
49 | function saveFile(filename, content) {
50 | files[filename] = content
51 | config.set(storeName, files)
52 | }
53 |
54 | function removeFile(filename) {
55 | const toRemove = Array.from(select.children).find(c => c.value === filename)
56 | select.removeChild(toRemove)
57 | delete files[filename]
58 | config.set(storeName, files)
59 | textarea.disabled = select.children.length === 0
60 | }
61 |
62 | function showFile(filename) {
63 | textarea.value = files[filename] || ""
64 | }
65 |
66 |
67 | /* Handle file Selection */
68 | for (const filename of Object.keys(files)) {
69 | select.appendChild(createOption(filename))
70 | }
71 |
72 |
73 | /* Handle events */
74 | select.addEventListener("change", ev => {
75 | showFile(select.value)
76 | })
77 |
78 | deleteBtn.addEventListener("click", ev => {
79 | ev.preventDefault()
80 | removeFile(select.value)
81 | showFile(select.value)
82 | })
83 |
84 | saveBtn.addEventListener("click", ev => {
85 | ev.preventDefault()
86 | const filename = select.value
87 | const content = textarea.value
88 | saveFile(filename, content)
89 | addShadow()
90 | })
91 |
92 | newBtn.addEventListener("click", ev => {
93 | ev.preventDefault()
94 | const response = window.prompt("name ?", defaultName).trim()
95 | if (!response) return
96 | const filename = preventDuplicate(files, response)
97 | addFile(filename, "")
98 | })
99 |
100 | editBtn.addEventListener("click", ev => {
101 | ev.preventDefault()
102 | const oldName = select.value
103 | const response = window.prompt("rename to ?", oldName).trim()
104 | if (!response || response === oldName) return
105 | const newName = preventDuplicate(files, response)
106 | const oldContent = files[oldName]
107 | removeFile(oldName)
108 | addFile(newName, oldContent)
109 | })
110 |
111 | textarea.addEventListener("keydown", ev => {
112 |
113 | /* ctrl+s -> save */
114 | if (ev.ctrlKey && ev.key === "s") {
115 | ev.preventDefault()
116 | const filename = select.value
117 | const content = textarea.value
118 | saveFile(filename, content)
119 | }
120 |
121 | addShadow()
122 | })
123 |
124 | textarea.addEventListener("keyup", addShadow)
125 |
126 |
127 | showFile(select.value)
128 | textarea.disabled = select.children.length === 0
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/firefox/src/devtools/panel/panel.js:
--------------------------------------------------------------------------------
1 |
2 | /* Create a filter function and store the compiled version */
3 | const func_cache = {}
4 | async function getFilterFunction(config) {
5 | const funcName = await config.get("activeMessageFunc")
6 | const funcSrc = (await config.get("savedMessageFunc"))[funcName] || "return data"
7 | if (!(funcSrc in func_cache)) {
8 | func_cache[funcSrc] = new Function("data", "origin", "destination", funcSrc)
9 | }
10 | return func_cache[funcSrc]
11 | }
12 |
13 | function createCell(txt) {
14 | const cell = document.createElement("span")
15 | cell.innerText = txt
16 | return cell
17 | }
18 |
19 | function createDetailsContent(origin, destination, message) {
20 | const content = document.createElement("div")
21 | content.classList.add("message-details-content")
22 |
23 |
24 | const oriTitle = createCell("Origin")
25 | const ori = document.createElement("span")
26 | ori.innerText = origin
27 |
28 |
29 | const destTitle = createCell("Destination")
30 | const dest = document.createElement("span")
31 | dest.innerText = destination
32 |
33 | const msgTitle = createCell("Message")
34 | const msg = document.createElement("span")
35 | msg.innerText = typeof message === "string" ? message : JSON.stringify(message, null, 2)
36 |
37 |
38 | content.appendChild(oriTitle)
39 | content.appendChild(ori)
40 | content.appendChild(destTitle)
41 | content.appendChild(dest)
42 | content.appendChild(msgTitle)
43 | content.appendChild(msg)
44 | return content
45 | }
46 |
47 | function stripProtocol(s) {
48 | if (s.startsWith('http://')) {
49 | return s.substr(7)
50 | }
51 | if (s.startsWith('https://')) {
52 | return s.substr(8)
53 | }
54 | return s
55 | }
56 |
57 | function createRow(origin, dest, msg, time) {
58 | const details = document.createElement("details")
59 | const summary = document.createElement("summary")
60 |
61 | details.open = false
62 | summary.classList.add("row")
63 | summary.appendChild(createCell(""))
64 | summary.appendChild(createCell(stripProtocol(origin)))
65 | summary.appendChild(createCell(stripProtocol(dest)))
66 | summary.appendChild(createCell(typeof msg === "string" ? msg : JSON.stringify(msg)))
67 | summary.appendChild(createCell(time))
68 | details.appendChild(summary)
69 | details.appendChild(createDetailsContent(origin, dest, msg))
70 | return details
71 | }
72 |
73 | function addRow(origin, dest, msg, time) {
74 | const container = document.querySelector("#message-list")
75 | container.appendChild(createRow(origin, dest, msg, time))
76 | }
77 |
78 | function createMessageHandler(config) {
79 | return async function handleMessage(message) {
80 | const date = new Date()
81 | const h = date.getHours().toString().padStart(2, "0")
82 | const m = date.getMinutes().toString().padStart(2, "0")
83 | const s = date.getSeconds().toString().padStart(2, "0")
84 | const time = `${h}:${m}:${s}`
85 | const filterFunction = await getFilterFunction(config)
86 | try {
87 | const data = filterFunction(message.data, message.origin, message.destination)
88 | if (data === null) return
89 | addRow(message.origin, message.destination, data, time)
90 | } catch (e) {
91 | addRow(message.origin, message.destination, `ERROR: ${e.toString()}`, time)
92 | }
93 |
94 | }
95 | }
96 |
97 |
98 | async function main() {
99 | const $ = sel => document.querySelector(sel)
100 | const $$ = sel => Array.from(document.querySelectorAll(sel))
101 |
102 | window.handleMessage = createMessageHandler(config)
103 |
104 | /* Top left buttons */
105 | $("#btn-clear").addEventListener("click", () => {
106 | $("#message-list").innerHTML = ""
107 | })
108 | $("#btn-shrink").addEventListener("click", () => {
109 | Array.from($$("details")).forEach(el => el.open = false)
110 | })
111 | $("#btn-expand").addEventListener("click", () => {
112 | Array.from($$("details")).forEach(el => el.open = true)
113 | })
114 |
115 |
116 | /* toggle panel */
117 | if (await config.get("devToolDual")) {
118 | $("main").classList.add("dual")
119 | }
120 | $("#toggleDual").addEventListener("click", () => {
121 | $("main").classList.toggle("dual")
122 | config.set("devToolDual", $("main").classList.contains("dual"))
123 | })
124 |
125 |
126 | /* Right panel */
127 | newFileSelection(config, "savedMessageFunc", "#savedMessageFunc", "filter")
128 |
129 | const select = $("#savedMessageFunc select")
130 | const activeMessageFunc = await config.get("activeMessageFunc")
131 |
132 | Array.from(select.children).find(c => c.selected = c.value === activeMessageFunc)
133 | select.addEventListener("change", () => {
134 | config.set("activeMessageFunc", select.value)
135 | })
136 |
137 | const textarea = $("#savedMessageFunc textarea")
138 | textarea.value = (await config.get("savedMessageFunc"))[activeMessageFunc] || ""
139 | }
140 |
141 | window.addEventListener("DOMContentLoaded", main)
--------------------------------------------------------------------------------
/firefox/src/devtools/panel/panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 | Id
31 | Origin
32 | Destination
33 | Message
34 | Time
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Filter function
42 |
43 |
44 |
46 |
47 |
54 |
55 |
62 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/firefox/src/features.js:
--------------------------------------------------------------------------------
1 | class Feature {
2 | constructor(config, configName) {
3 | this.config = config
4 | this.configName = configName
5 | this.started = false
6 | config.onChange(configName, v => {
7 | v ? this.start() : this.stop()
8 | })
9 | }
10 |
11 | async maybeStart() {
12 | if (await this.config.get(this.configName)) {
13 | this.start();
14 | } else {
15 | this.stop();
16 | }
17 | }
18 |
19 | start() {
20 | this.started = true
21 | }
22 |
23 | stop() {
24 | this.started = false
25 | }
26 | }
27 |
28 |
29 | /* Burp Proxy */
30 |
31 | function proxify(config, onlyContainers) {
32 | return async function (e) {
33 | if (onlyContainers && e.cookieStoreId == 'firefox-default')
34 | return { type: "direct" };
35 | const host = await config.get("burpProxyHost")
36 | const port = await config.get("burpProxyPort")
37 | return {
38 | type: "http",
39 | host,
40 | port
41 | };
42 | }
43 | }
44 |
45 |
46 |
47 | class UseBurpProxyAll extends Feature {
48 | constructor(config) {
49 | super(config, 'useBurpProxyAll')
50 | this.proxy = proxify(config, false)
51 | }
52 |
53 | async start() {
54 | super.start()
55 | if (!await this.config.get("enabled")) return
56 |
57 | browser.proxy.onRequest.addListener(this.proxy, { urls: [""] })
58 |
59 | }
60 |
61 | stop() {
62 | browser.proxy.onRequest.removeListener(this.proxy)
63 | super.stop()
64 | }
65 | }
66 |
67 |
68 | class UseBurpProxyContainers extends Feature {
69 | constructor(config) {
70 | super(config, 'useBurpProxyContainer')
71 | this.proxy = proxify(config, true)
72 | }
73 |
74 | async start() {
75 | super.start()
76 | if (!await this.config.get("enabled")) return
77 |
78 | browser.proxy.onRequest.addListener(this.proxy, { urls: [""] })
79 | }
80 |
81 | stop() {
82 | super.stop()
83 | browser.proxy.onRequest.removeListener(this.proxy)
84 | }
85 | }
86 |
87 |
88 | /* Add Color Headers */
89 |
90 |
91 | async function colorHeaderHandler(e) {
92 | if (e.tabId < 0) return
93 |
94 | const colorMap = {
95 | blue: "blue",
96 | turquoise: "cyan",
97 | green: "green",
98 | yellow: "yellow",
99 | orange: "orange",
100 | red: "red",
101 | pink: "pink",
102 | purple: "magenta",
103 | }
104 | const { cookieStoreId } = await browser.tabs.get(e.tabId)
105 | if (cookieStoreId === "firefox-default") {
106 | return {}
107 | }
108 | const identity = await browser.contextualIdentities.get(cookieStoreId)
109 | if (identity.name.startsWith("PwnFox-")) {
110 | const name = "X-PwnFox-Color"
111 | const value = colorMap[identity.color]
112 | e.requestHeaders.push({ name, value })
113 | }
114 | return { requestHeaders: e.requestHeaders }
115 | }
116 |
117 | class AddContainerHeader extends Feature {
118 | constructor(config) {
119 | super(config, 'addContainerHeader')
120 | }
121 |
122 | async start() {
123 | super.start()
124 | if (!await this.config.get("enabled")) return
125 |
126 | browser.webRequest.onBeforeSendHeaders.addListener(colorHeaderHandler,
127 | { urls: [""] },
128 | ["blocking", "requestHeaders"]
129 | );
130 | }
131 |
132 | stop() {
133 | browser.webRequest.onBeforeSendHeaders.removeListener(colorHeaderHandler)
134 | super.stop()
135 | }
136 | }
137 |
138 |
139 | /* Remove security Headers */
140 | function removeHeaders(response) {
141 | const { responseHeaders: origHeaders } = response
142 | const blacklistedHeaders = [
143 | "Content-Security-Policy",
144 | "X-XSS-Protection",
145 | "X-Frame-Options",
146 | "X-Content-Type-Options"
147 | ]
148 | const newHeaders = origHeaders.filter(({ name }) => {
149 | return !blacklistedHeaders.includes(name)
150 | })
151 | return { responseHeaders: newHeaders }
152 | }
153 |
154 |
155 | class RemoveSecurityHeaders extends Feature {
156 | constructor(config) {
157 | super(config, 'removeSecurityHeaders')
158 | }
159 |
160 | async start() {
161 | super.start()
162 | if (!await this.config.get("enabled")) return
163 |
164 | browser.webRequest.onHeadersReceived.addListener(removeHeaders,
165 | { urls: [""] },
166 | ["blocking", "responseHeaders"]
167 | );
168 | }
169 |
170 | stop() {
171 | super.stop()
172 | browser.webRequest.onHeadersReceived.removeListener(removeHeaders)
173 | }
174 | }
175 |
176 | /* Toolbox */
177 |
178 | class InjectToolBox extends Feature {
179 | constructor(config) {
180 | super(config, "injectToolbox")
181 | this.script = null
182 | config.onChange("activeToolbox", () => this.maybeStart())
183 | config.onChange("savedToolbox", () => this.maybeStart())
184 | }
185 |
186 |
187 | async start() {
188 | super.start()
189 | if (!await this.config.get("enabled")) return
190 |
191 |
192 |
193 | const toolboxName = await this.config.get("activeToolbox")
194 | const toolbox = (await this.config.get("savedToolbox"))[toolboxName] || ""
195 |
196 | if (this.script) {
197 | this.script.unregister()
198 | }
199 |
200 | this.script = await browser.contentScripts.register({
201 | allFrames: true,
202 | matches: [""],
203 | runAt: "document_start",
204 | js: [{
205 | code: toolbox,
206 | }]
207 | })
208 | }
209 |
210 | stop() {
211 | super.stop()
212 | if (this.script) {
213 | this.script.unregister()
214 | }
215 | }
216 |
217 | }
218 |
219 |
220 | /* Post Message */
221 | function logMessage({ data, origin }) {
222 | browser.runtime.sendMessage({ data, origin, destination: window.origin })
223 | }
224 |
225 | class LogPostMessage extends Feature {
226 | constructor(config) {
227 | super(config, "logPostMessage")
228 | }
229 |
230 | async start() {
231 | super.start()
232 | if (!await this.config.get("enabled")) return
233 | window.addEventListener("message", logMessage);
234 |
235 | }
236 |
237 | stop() {
238 | super.stop()
239 | window.removeEventListener("message", logMessage);
240 | }
241 | }
242 |
243 | /* Global Enable */
244 |
245 | class FeaturesGroup extends Feature {
246 | constructor(config, features) {
247 | super(config, "enabled")
248 | this.features = features
249 | }
250 |
251 | start() {
252 | super.start()
253 | this.features.forEach(f => f.maybeStart())
254 | }
255 |
256 | stop() {
257 | super.stop()
258 | this.features.forEach(f => f.stop())
259 | }
260 | }
261 |
262 |
263 | class BackgroundFeatures extends FeaturesGroup {
264 | constructor(config) {
265 | const features = [
266 | new UseBurpProxyContainers(config),
267 | new UseBurpProxyAll(config),
268 | new AddContainerHeader(config),
269 | new InjectToolBox(config),
270 | new RemoveSecurityHeaders(config),
271 | ]
272 | super(config, features)
273 | }
274 |
275 | start() {
276 | super.start()
277 | createIcon("#00ff00").then(([canvas, imageData]) => {
278 | browser.browserAction.setIcon({ imageData })
279 | })
280 | }
281 |
282 | stop() {
283 | super.stop()
284 | createIcon("#ff0000").then(([canvas, imageData]) => {
285 | browser.browserAction.setIcon({ imageData })
286 | })
287 | }
288 | }
289 |
290 |
291 | class ContentScriptFeatures extends FeaturesGroup {
292 | constructor(config) {
293 | const features = [
294 | new LogPostMessage(config),
295 | ]
296 | super(config, features)
297 | }
298 | }
--------------------------------------------------------------------------------