Skip to content

HUD Startup Flow

thc202 edited this page Jan 19, 2021 · 3 revisions

In Zap Desktop

  • While HUD is turned on, Browser Quick Launch is pressed starting up a browser

In Browser

  • User navigates to a target web page
  • http request is sent

In ZAP Desktop

  • http request is proxied with no changes
  • http response is proxied and ZAP modifies all html responses to include the "injectionHtml.html" source (a single script tag) added right below the openning tag in the response
               // These are the only files that use FILE_PREFIX
                hudScript =
                        hudScript.replace("<<FILE_PREFIX>>", api.getUrlPrefix(api.getSite(msg)));
                htmlEd.injectAtStartOfBody(hudScript);
                htmlEd.rewriteHttpMessage();

https://github.com/zaproxy/zap-hud/blob/main/src/main/java/org/zaproxy/zap/extension/hud/ExtensionHUD.java#L401`

In Browser

  • Browser parses the html response
  • when the browser parsing reaches the inject script tag it asynchronously requests the javascript file inject.js

In ZAP Desktop

  • ZAP intercepts the request for the inject.js page and returns it

In Browser

  • When the browser receives the javascript response, it begins executing the file

In inject.js

  • the inject.js files checks whether the browser window it was loaded into is the "top window" and if not, does nothing more (this will be for iframes in the target page that get the inject script added)
  • generates tabId
  • adds postMessage listener
  • adds iframes to DOM
  • updates URL
if (window.top == window.self) {
		tabId = generateTabId();

		window.addEventListener("message", receiveMessages);

		var template = document.createElement("template");
		template.innerHTML = '<iframe id="management" src="' + ZAP_HUD_FILES + '?name=management.html&amp;frameId=management&amp;tabId=' + tabId + '" scrolling="no" style="position: fixed; right: 0px; bottom: 50px; width:28px; height:60px; border: medium none; overflow: hidden; z-index: 2147483647"></iframe>\n' +
			'<iframe id="left-panel" src="' + ZAP_HUD_FILES + '?name=panel.html&amp;url=' + URL + '&amp;orientation=left&amp;frameId=leftPanel&amp;tabId=' + tabId + '" scrolling="no" style="position: fixed; border: medium none; top: 30%; border: medium none; left: 0px; width: 110px; height: 300px; z-index: 2147483646;"></iframe>\n' +
			'<iframe id="right-panel" src="' + ZAP_HUD_FILES + '?name=panel.html&amp;url=' + URL + '&amp;orientation=right&amp;frameId=rightPanel&amp;tabId=' + tabId + '" scrolling="no" style="position: fixed; border: medium none; top: 30%; overflow: hidden; right: 0px; width: 110px; height: 300px; z-index: 2147483646;"></iframe>\n' +
			'<iframe id="bottom-drawer" src="' + ZAP_HUD_FILES + '?name=drawer.html&amp;frameId=drawer&amp;tabId=' + tabId + '" scrolling="no" style="position: fixed; border: medium none; overflow: hidden; left: 0px; bottom: 0px; width: 100%; height: 50px; z-index: 2147483646;"></iframe>\n' +
			'<iframe id="main-display" src="' + ZAP_HUD_FILES + '?name=display.html&amp;frameId=display&amp;tabId=' + tabId + '" style="position: fixed; right: 0px; top: 0px; width: 100%; height: 100%; border: 0px none; display: none; z-index: 2147483647;"></iframe>\n' +
			'<iframe id="growler-alerts" src="' + ZAP_HUD_FILES + '?name=growlerAlerts.html&amp;frameId=growlerAlerts&amp;tabId=' + tabId + '" style="position: fixed; right: 0px; bottom: 0px; width: 500px; height: 0px;border: 0px none; z-index: 2147483647;"></iframe>';
		document.body.appendChild(template.content);

https://github.com/zaproxy/zap-hud/blob/main/src/main/zapHomeFiles/hud/target/inject.js#L362

In management.js

  • When the management.html iframe is added to the DOM if loads management.js
  • management.js loads a vue copmonent
  • on DomContentLoaded event
  • if navigator.serviceworker.controller == null (if there is no serviceworker)
  • hide all of the display frames (by sending postMessage to inject.js to hide the iframes)
	// if first time starting HUD boot up the service worker
	if (navigator.serviceWorker.controller === null) {
		// temp time test
		localforage.setItem('starttime', startTime)

		parent.postMessage( {action: 'hideAllDisplayFrames'} , document.referrer);

		localforage.setItem('is_first_load', true)

		startServiceWorker();

https://github.com/zaproxy/zap-hud/blob/bac4ac21c18996f824c1bc6003a8060b23c76412/src/main/zapHomeFiles/hud/management.js#L66

  • register the serviceworker
  • Waits until the navigator.serviceworker.controller.ready promise resolves to true
function startServiceWorker() {
	if ('serviceWorker' in navigator) {
		navigator.serviceWorker.register(utils.getZapFilePath('serviceworker.js'))
			.then(registration => {
				utils.log(LOG_INFO, 'Service worker registration was successful for the scope: ' + registration.scope);

				// wait until serviceworker is installed and activated
				return navigator.serviceWorker.ready;
			})

https://github.com/zaproxy/zap-hud/blob/bac4ac21c18996f824c1bc6003a8060b23c76412/src/main/zapHomeFiles/hud/management.js#L161

In serviceworker.js When the browser registers a serviceworker it loads the script and starts executing

  • import javascript files needed
importScripts(ZAP_HUD_FILES + "?name=libraries/localforage.min.js"); 
importScripts(ZAP_HUD_FILES + "?name=libraries/vue.js"); 
importScripts(ZAP_HUD_FILES + "?name=libraries/vue-i18n.js"); 
importScripts(ZAP_HUD_FILES + "?name=i18n.js");
importScripts(ZAP_HUD_FILES + "?name=utils.js");
importScripts(ZAP_HUD_FILES + "?name=tools/utils/alertUtils.js");

https://github.com/zaproxy/zap-hud/blob/main/src/main/zapHomeFiles/hud/serviceworker.js#L7

  • import tool scripts
// Load Tool Scripts
localforage.setItem("tools", [])
	.then(() => {
		toolScripts.forEach(script => {
			importScripts(script); 
		});
	})
	.then(() => {
		var ts = [];
		for (var tool in self.tools) {
			ts.push(self.tools[tool].name);
		}
		return utils.registerTools(ts); 

	})
	.catch(utils.errorHandler);

https://github.com/zaproxy/zap-hud/blob/main/src/main/zapHomeFiles/hud/serviceworker.js#L46

  • add event listeners
  • configured and start the web socket
/* Set up WebSockets */

{
	let ZAP_HUD_WS = '<<ZAP_HUD_WS>>';
	webSocket = new WebSocket(ZAP_HUD_WS);
}

https://github.com/zaproxy/zap-hud/blob/main/src/main/zapHomeFiles/hud/serviceworker.js#L173

  • browser finishes parsing/executing serviceworker.js file

In Browser

  • now that serviceworker.js file has been parsed in sends 'install' event to serviceworker

In serviceworker.js

  • the onInstall event handler is invoked
  • the serviceworker caches some files to be served up by the onFetch handler
  • the onInstall handler finishes
const onInstall = event => {
	utils.log(LOG_INFO, 'serviceworker.install', 'Installing...');

	// Cache Files
	event.waitUntil(
		caches.open(CACHE_NAME)
			.then(cache => {
				console.log("caching urls...");
				return cache.addAll(urlsToCache);
			})
			.catch(utils.errorHandler)
	);
};

https://github.com/zaproxy/zap-hud/blob/main/src/main/zapHomeFiles/hud/serviceworker.js#L63

In Browser

  • now that the install event has been handled, it sends the 'activate' event to the serviceworker

In serviceworker.js

  • the onActivate handler is invoked
  • the serviceworker run the initializeHud function which sets a bunch of things in indexedDb
  • the onActivate handler finishes
const onActivate = event => {
	// Check Storage & Initiate
	event.waitUntil(
		utils.isStorageConfigured()
			.then(isConfigured => {

				if (!isConfigured || isDebugging) {
					return utils.configureStorage();
				}
			})
			.then(() => {
				// set the default tools after configuring storage
				utils.setDefaultTools();
			})
			.catch(utils.errorHandler)
	);
};

https://github.com/zaproxy/zap-hud/blob/main/src/main/zapHomeFiles/hud/serviceworker.js#L77

In browser

  • now that the activate event hadn been handled, it sets resolves the navigator.serviceworker.controller.ready promise

In management.js

  • now that navigator.serviceworker.controller.ready had resolved, it refreshes all of the display frames by sending a postMessage to the inject.js file to refresh the iframes
  • And then does nothing
			.then(() => {
				// refresh the frames so the service worker can take control
				parent.postMessage( {action: 'refreshAllFrames'} , document.referrer);
			})
			.catch(utils.errorHandler);

https://github.com/zaproxy/zap-hud/blob/bac4ac21c18996f824c1bc6003a8060b23c76412/src/main/zapHomeFiles/hud/management.js#L172

In inject.js

  • receiving the postMessage to refresh the iframes, it does so including the management.js file

In management.js

  • After being refreshed it follows the same first step
  • A vue component is created
  • serviceworker != null now
  • show all display frames (by sending postMessage to inject.js
  • set IS_SERVICEWORKER_REFRESHED so that errors start to be logged
  • if its the first time the HUD has been loaded and the welcome screen hasn't been hidden, show the welcome screen
  • add postMessage listener from serviceworker and target page
  • notify service worker vis postMessage that the target is loaded
  • start heart beat loop from managemnt.js to serviceworker
parent.postMessage( {action: 'showAllDisplayFrames'} , document.referrer);

		// temp time test
		localforage.getItem('starttime')
			.then(startT => {
				let currentTime = new Date().getTime();
				let diff = currentTime - parseInt(startT);
				console.log('Time (ms) to load UI: ' + diff)
			})

		localforage.setItem(IS_SERVICEWORKER_REFRESHED, true);
		localforage.getItem('is_first_load')
			.then(isFirstLoad => {
				localforage.setItem('is_first_load', false)

				if (isFirstLoad && SHOW_WELCOME_SCREEN) {
					parent.postMessage( {action: 'expandManagement'} , document.referrer);
					app.showWelcomeScreen = true;
				}
			})

		window.addEventListener('message', windowMessageListener)
		navigator.serviceWorker.addEventListener('message', serviceWorkerMessageListener)
		navigator.serviceWorker.controller.postMessage({action: 'targetload', tabId: tabId, targetUrl: context.url});

https://github.com/zaproxy/zap-hud/blob/bac4ac21c18996f824c1bc6003a8060b23c76412/src/main/zapHomeFiles/hud/management.js#L76