├── .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 | 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 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 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 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1619611912415 23 | 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 | ![popup](/screenshots/popup.png) 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 | ![tabs](/screenshots/tabs.png) 38 | ![burp](/screenshots/burp.png) 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 | ![](/screenshots/post-single.png) 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 | ![](/screenshots/post-dual.png) 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 | ![settings](/screenshots/settings.png) 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 |
13 |

Burp

14 |
    15 |
  • 16 | 17 | 18 |
  • 19 |
  • 20 | 21 | 22 |
  • 23 |
24 | 25 |
26 |

Toolbox

27 |
28 |
29 | 31 | 32 | 39 | 40 | 47 | 66 |
67 |
68 | 69 |
70 |
71 | 72 |
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 |
16 | 17 | 18 | 19 |
20 | 22 | 26 | 27 |
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 | 81 |
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 | } --------------------------------------------------------------------------------