|
| 1 | +/** |
| 2 | + * FrogPost Extension |
| 3 | + * Originally Created by thisis0xczar/Lidor JFrog AppSec Team |
| 4 | + * Refined on: 2025-04-15 |
| 5 | + */ |
| 6 | +(() => { |
| 7 | + const MONITOR_FLAG = '__frogPostMonitorInjected_v3'; |
| 8 | + const CONSOLE_FLAG = '__frogPostConsoleHooked_v2'; |
| 9 | + if (window[MONITOR_FLAG]) { |
| 10 | + return; |
| 11 | + } |
| 12 | + window[MONITOR_FLAG] = true; |
| 13 | + |
| 14 | + const CONSOLE_MARKER = "FROGPWNED_CONSOLE_XSS"; |
| 15 | + let lastKnownPayloadIndexFromFuzzer = -1; |
| 16 | + |
| 17 | + try { |
| 18 | + window.addEventListener('message', (event) => { |
| 19 | + if (event.source === window.parent && event.data && event.data.type === '__FROGPOST_SET_INDEX__' && typeof event.data.index === 'number') { |
| 20 | + lastKnownPayloadIndexFromFuzzer = event.data.index; |
| 21 | + } |
| 22 | + }, false); |
| 23 | + } catch(e) { |
| 24 | + console.error("FrogPost Monitor: Failed to add index listener", e); |
| 25 | + } |
| 26 | + |
| 27 | + if (!window[CONSOLE_FLAG]) { |
| 28 | + try { |
| 29 | + const originalConsoleLog = window.console.log; |
| 30 | + window.console.log = function(...args) { |
| 31 | + let markerFound = false; |
| 32 | + let detectedPayloadIndex = lastKnownPayloadIndexFromFuzzer; |
| 33 | + try { |
| 34 | + if (args.some(arg => typeof arg === 'string' && arg.includes(CONSOLE_MARKER))) { |
| 35 | + markerFound = true; |
| 36 | + if (chrome?.runtime?.id) { |
| 37 | + chrome.runtime.sendMessage({ |
| 38 | + type: "FROGPOST_CONSOLE_SUCCESS", |
| 39 | + detail: { markerFound: true, firstArg: String(args[0]).substring(0, 100), timestamp: new Date().toISOString() }, |
| 40 | + location: window.location.href, |
| 41 | + payloadIndex: detectedPayloadIndex |
| 42 | + }).catch(e => {}); |
| 43 | + } |
| 44 | + } |
| 45 | + } catch (e) { |
| 46 | + console.warn("FrogPost Monitor: Error processing console log hook", e); |
| 47 | + } |
| 48 | + originalConsoleLog.apply(console, args); |
| 49 | + }; |
| 50 | + window[CONSOLE_FLAG] = true; |
| 51 | + } catch (e) { |
| 52 | + console.error("FrogPost Monitor: Failed to hook console.log", e); |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + const SUSPICIOUS_TAGS = new Set(['SCRIPT', 'IFRAME', 'OBJECT', 'EMBED', 'APPLET', 'VIDEO', 'AUDIO', 'LINK', 'FORM', 'DETAILS', 'MARQUEE', 'SVG', 'MATH', 'BUTTON']); |
| 57 | + const SUSPICIOUS_ATTRS = new Set(['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onpageshow', 'onwheel', 'ontoggle', 'onbegin', 'formaction', 'srcdoc', 'background', 'style']); |
| 58 | + const SUSPICIOUS_ATTR_VALUES = /^(javascript:|vbscript:|data:)/i; |
| 59 | + const SUSPICIOUS_SRC_HREF_ATTRS = new Set(['src', 'href', 'action', 'formaction', 'background', 'data']); |
| 60 | + |
| 61 | + function getElementDescription(node) { if (!node || node.nodeType !== Node.ELEMENT_NODE) return 'NonElementNode'; let desc = `<${node.nodeName.toLowerCase()}`; for (const attr of node.attributes) { desc += ` ${attr.name}="${String(attr.value || '').substring(0, 20)}..."`; } return desc.substring(0, 100) + (desc.length > 100 ? '>...' : '>'); } |
| 62 | + |
| 63 | + function isSuspiciousMutation(mutation) { try { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { const nodeName = node.nodeName.toUpperCase(); if (SUSPICIOUS_TAGS.has(nodeName)) { return { reason: `Added suspicious tag: <${nodeName}>`, nodeInfo: node.outerHTML?.substring(0, 150) }; } if (node.matches && node.matches('[onerror], [onload], [onclick], [onmouseover], [onfocus]')) { return { reason: `Added node with suspicious event handler`, nodeInfo: node.outerHTML.substring(0, 100) }; } const suspiciousAttr = node.getAttributeNames().find(attr => SUSPICIOUS_ATTRS.has(attr.toLowerCase())); if(suspiciousAttr) { return { reason: `Added node with suspicious attribute: ${suspiciousAttr}`, nodeInfo: getElementDescription(node), attributeValue: node.getAttribute(suspiciousAttr)?.substring(0, 50) }; } for(const attrName of node.getAttributeNames()) { const lowerAttrName = attrName.toLowerCase(); if (SUSPICIOUS_SRC_HREF_ATTRS.has(lowerAttrName)) { const value = node.getAttribute(attrName); if(value && SUSPICIOUS_ATTR_VALUES.test(value)) { return { reason: `Added node with suspicious protocol in attribute: ${lowerAttrName}`, nodeInfo: getElementDescription(node), attributeValue: value.substring(0, 50) }; } } } if (nodeName === 'SCRIPT' && node.innerHTML?.length > 0) { return { reason: `Added script tag with content`, nodeInfo: node.outerHTML?.substring(0, 150) }; } } } } else if (mutation.type === 'attributes') { const attrName = mutation.attributeName?.toLowerCase(); const targetNode = mutation.target; if (targetNode?.nodeType !== Node.ELEMENT_NODE) return null; const targetDesc = getElementDescription(targetNode); if (SUSPICIOUS_ATTRS.has(attrName)) { const value = targetNode.getAttribute(mutation.attributeName); return { reason: `Suspicious attribute modified/added: ${attrName}`, target: targetNode.nodeName, value: value?.substring(0, 100), nodeInfo: targetDesc }; } if (SUSPICIOUS_SRC_HREF_ATTRS.has(attrName)) { const value = targetNode.getAttribute(mutation.attributeName); if(value && SUSPICIOUS_ATTR_VALUES.test(value)) { return { reason: `Suspicious protocol set for attribute: ${attrName}`, target: targetNode.nodeName, value: value.substring(0, 100), nodeInfo: targetDesc }; } } } } catch(e) { console.warn("FrogPost Monitor: Error checking mutation", e); } return null; } |
| 64 | + |
| 65 | + const observerCallback = (mutationsList, observer) => { |
| 66 | + let currentPayloadIndex = lastKnownPayloadIndexFromFuzzer; // Capture index at time of mutation batch |
| 67 | + for (const mutation of mutationsList) { |
| 68 | + const suspiciousDetail = isSuspiciousMutation(mutation); |
| 69 | + if (suspiciousDetail) { |
| 70 | + try { |
| 71 | + suspiciousDetail.timestamp = new Date().toISOString(); |
| 72 | + if (chrome?.runtime?.id) { |
| 73 | + chrome.runtime.sendMessage({ type: "FROGPOST_MUTATION", detail: suspiciousDetail, location: window.location.href, payloadIndex: currentPayloadIndex }).catch(e => {}); |
| 74 | + } else { observer.disconnect(); break; } |
| 75 | + } catch (e) { console.warn("FrogPost Monitor: Failed to send mutation message", e); } |
| 76 | + } |
| 77 | + } |
| 78 | + }; |
| 79 | + |
| 80 | + const observer = new MutationObserver(observerCallback); |
| 81 | + const config = { attributes: true, childList: true, subtree: true, attributeOldValue: false }; |
| 82 | + |
| 83 | + const startObserving = () => { |
| 84 | + const initialTarget = document.documentElement; |
| 85 | + let bodyObserverActive = false; |
| 86 | + const observeBody = () => { if (document.body && !bodyObserverActive) { try { observer.disconnect(); } catch(e){} try { observer.observe(document.body, config); bodyObserverActive = true; } catch(e) { console.error("FrogPost Monitor: Failed to observe document.body", e); } } }; |
| 87 | + try { observer.observe(initialTarget, { childList: true, subtree: true }); } catch(e) { console.error("FrogPost Monitor: Failed to observe documentElement", e); return; } |
| 88 | + if (document.body) { observeBody(); } |
| 89 | + else { const bodyWaitObserver = new MutationObserver(() => { if (document.body) { bodyWaitObserver.disconnect(); observeBody(); } }); try { bodyWaitObserver.observe(document.documentElement, { childList: true }); } catch(e) { console.error("FrogPost Monitor: Failed to observe documentElement for body wait", e); if(document.body) observeBody(); } } |
| 90 | + }; |
| 91 | + |
| 92 | + if (document.readyState === 'loading') { |
| 93 | + document.addEventListener('DOMContentLoaded', startObserving, { once: true }); |
| 94 | + } else { |
| 95 | + startObserving(); |
| 96 | + } |
| 97 | + |
| 98 | +})(); |
0 commit comments