Initial commit
Dieser Commit ist enthalten in:
481
infrastructure/services/fingerprint/browser_injection_service.py
Normale Datei
481
infrastructure/services/fingerprint/browser_injection_service.py
Normale Datei
@ -0,0 +1,481 @@
|
||||
"""
|
||||
Browser Injection Service - Handles fingerprint injection into browser contexts.
|
||||
"""
|
||||
|
||||
import json
|
||||
import base64
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from domain.entities.browser_fingerprint import BrowserFingerprint
|
||||
|
||||
|
||||
class BrowserInjectionService:
|
||||
"""Service for injecting fingerprints into browser contexts."""
|
||||
|
||||
def generate_fingerprint_scripts(self, fingerprint: BrowserFingerprint) -> Dict[str, str]:
|
||||
"""Generate all fingerprint injection scripts."""
|
||||
return {
|
||||
"canvas_protection": self._generate_canvas_script(fingerprint),
|
||||
"webgl_protection": self._generate_webgl_script(fingerprint),
|
||||
"webrtc_protection": self._generate_webrtc_script(fingerprint),
|
||||
"navigator_override": self._generate_navigator_script(fingerprint),
|
||||
"hardware_override": self._generate_hardware_script(fingerprint),
|
||||
"timezone_override": self._generate_timezone_script(fingerprint),
|
||||
"audio_protection": self._generate_audio_script(fingerprint),
|
||||
"font_detection": self._generate_font_script(fingerprint),
|
||||
"plugin_override": self._generate_plugin_script(fingerprint)
|
||||
}
|
||||
|
||||
def _generate_canvas_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate canvas fingerprint protection script."""
|
||||
return f'''
|
||||
(function() {{
|
||||
const seed = {fingerprint.canvas_noise.seed};
|
||||
const noiseLevel = {fingerprint.canvas_noise.noise_level};
|
||||
const algorithm = "{fingerprint.canvas_noise.algorithm}";
|
||||
|
||||
// Deterministic random based on seed
|
||||
let randomSeed = seed;
|
||||
function seededRandom() {{
|
||||
randomSeed = (randomSeed * 9301 + 49297) % 233280;
|
||||
return randomSeed / 233280;
|
||||
}}
|
||||
|
||||
// Override toDataURL
|
||||
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
||||
HTMLCanvasElement.prototype.toDataURL = function(...args) {{
|
||||
const context = this.getContext('2d');
|
||||
if (context) {{
|
||||
const imageData = context.getImageData(0, 0, this.width, this.height);
|
||||
const data = imageData.data;
|
||||
|
||||
// Apply noise based on algorithm
|
||||
for (let i = 0; i < data.length; i += 4) {{
|
||||
if (algorithm === 'gaussian') {{
|
||||
// Gaussian noise
|
||||
const noise = (seededRandom() - 0.5) * 2 * noiseLevel * 255;
|
||||
data[i] = Math.max(0, Math.min(255, data[i] + noise));
|
||||
data[i+1] = Math.max(0, Math.min(255, data[i+1] + noise));
|
||||
data[i+2] = Math.max(0, Math.min(255, data[i+2] + noise));
|
||||
}} else if (algorithm === 'uniform') {{
|
||||
// Uniform noise
|
||||
const noise = (seededRandom() - 0.5) * noiseLevel * 255;
|
||||
data[i] = Math.max(0, Math.min(255, data[i] + noise));
|
||||
}}
|
||||
}}
|
||||
|
||||
context.putImageData(imageData, 0, 0);
|
||||
}}
|
||||
|
||||
return originalToDataURL.apply(this, args);
|
||||
}};
|
||||
|
||||
// Override getImageData
|
||||
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
|
||||
CanvasRenderingContext2D.prototype.getImageData = function(...args) {{
|
||||
const imageData = originalGetImageData.apply(this, args);
|
||||
const data = imageData.data;
|
||||
|
||||
// Apply same noise
|
||||
for (let i = 0; i < data.length; i += 4) {{
|
||||
const noise = (seededRandom() - 0.5) * noiseLevel * 255;
|
||||
data[i] = Math.max(0, Math.min(255, data[i] + noise));
|
||||
}}
|
||||
|
||||
return imageData;
|
||||
}};
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_webgl_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate WebGL fingerprint override script."""
|
||||
return f'''
|
||||
(function() {{
|
||||
const overrides = {{
|
||||
vendor: "{fingerprint.webgl_vendor}",
|
||||
renderer: "{fingerprint.webgl_renderer}"
|
||||
}};
|
||||
|
||||
// Override WebGL getParameter
|
||||
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
||||
WebGLRenderingContext.prototype.getParameter = function(parameter) {{
|
||||
if (parameter === 37445) {{ // UNMASKED_VENDOR_WEBGL
|
||||
return overrides.vendor;
|
||||
}}
|
||||
if (parameter === 37446) {{ // UNMASKED_RENDERER_WEBGL
|
||||
return overrides.renderer;
|
||||
}}
|
||||
return getParameter.apply(this, arguments);
|
||||
}};
|
||||
|
||||
// Same for WebGL2
|
||||
if (typeof WebGL2RenderingContext !== 'undefined') {{
|
||||
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
|
||||
WebGL2RenderingContext.prototype.getParameter = function(parameter) {{
|
||||
if (parameter === 37445) return overrides.vendor;
|
||||
if (parameter === 37446) return overrides.renderer;
|
||||
return getParameter2.apply(this, arguments);
|
||||
}};
|
||||
}}
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_webrtc_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate WebRTC protection script."""
|
||||
if fingerprint.webrtc_config.disable_webrtc:
|
||||
return '''
|
||||
(function() {
|
||||
// Completely disable WebRTC
|
||||
window.RTCPeerConnection = undefined;
|
||||
window.RTCSessionDescription = undefined;
|
||||
window.RTCIceCandidate = undefined;
|
||||
window.webkitRTCPeerConnection = undefined;
|
||||
window.mozRTCPeerConnection = undefined;
|
||||
})();
|
||||
'''
|
||||
else:
|
||||
return f'''
|
||||
(function() {{
|
||||
const localIPMask = "{fingerprint.webrtc_config.local_ip_mask}";
|
||||
|
||||
// Override RTCPeerConnection
|
||||
const OriginalRTCPeerConnection = window.RTCPeerConnection ||
|
||||
window.webkitRTCPeerConnection ||
|
||||
window.mozRTCPeerConnection;
|
||||
|
||||
if (OriginalRTCPeerConnection) {{
|
||||
window.RTCPeerConnection = function(config, constraints) {{
|
||||
const pc = new OriginalRTCPeerConnection(config, constraints);
|
||||
|
||||
// Override createDataChannel to prevent IP leak
|
||||
const originalCreateDataChannel = pc.createDataChannel;
|
||||
pc.createDataChannel = function(...args) {{
|
||||
return originalCreateDataChannel.apply(pc, args);
|
||||
}};
|
||||
|
||||
// Monitor ICE candidates
|
||||
pc.addEventListener('icecandidate', function(event) {{
|
||||
if (event.candidate && event.candidate.candidate) {{
|
||||
// Mask local IP addresses
|
||||
event.candidate.candidate = event.candidate.candidate.replace(
|
||||
/([0-9]{{1,3}}\.){{3}}[0-9]{{1,3}}/g,
|
||||
function(match) {{
|
||||
if (match.startsWith('10.') ||
|
||||
match.startsWith('192.168.') ||
|
||||
match.startsWith('172.')) {{
|
||||
return localIPMask;
|
||||
}}
|
||||
return match;
|
||||
}}
|
||||
);
|
||||
}}
|
||||
}});
|
||||
|
||||
return pc;
|
||||
}};
|
||||
|
||||
// Copy static properties
|
||||
Object.keys(OriginalRTCPeerConnection).forEach(key => {{
|
||||
window.RTCPeerConnection[key] = OriginalRTCPeerConnection[key];
|
||||
}});
|
||||
}}
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_navigator_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate navigator properties override script."""
|
||||
nav = fingerprint.navigator_props
|
||||
languages_json = json.dumps(nav.languages) if nav.languages else '["en-US", "en"]'
|
||||
|
||||
return f'''
|
||||
(function() {{
|
||||
// Navigator overrides
|
||||
Object.defineProperty(navigator, 'platform', {{
|
||||
get: () => "{nav.platform}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'vendor', {{
|
||||
get: () => "{nav.vendor}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'vendorSub', {{
|
||||
get: () => "{nav.vendor_sub}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'product', {{
|
||||
get: () => "{nav.product}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'productSub', {{
|
||||
get: () => "{nav.product_sub}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'appName', {{
|
||||
get: () => "{nav.app_name}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'appVersion', {{
|
||||
get: () => "{nav.app_version}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'userAgent', {{
|
||||
get: () => "{nav.user_agent}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'language', {{
|
||||
get: () => "{nav.language}"
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'languages', {{
|
||||
get: () => {languages_json}
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'onLine', {{
|
||||
get: () => {str(nav.online).lower()}
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'doNotTrack', {{
|
||||
get: () => "{nav.do_not_track}"
|
||||
}});
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_hardware_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate hardware properties override script."""
|
||||
hw = fingerprint.hardware_config
|
||||
|
||||
return f'''
|
||||
(function() {{
|
||||
// Hardware overrides
|
||||
Object.defineProperty(navigator, 'hardwareConcurrency', {{
|
||||
get: () => {hw.hardware_concurrency}
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'deviceMemory', {{
|
||||
get: () => {hw.device_memory}
|
||||
}});
|
||||
|
||||
Object.defineProperty(navigator, 'maxTouchPoints', {{
|
||||
get: () => {hw.max_touch_points}
|
||||
}});
|
||||
|
||||
// Screen overrides
|
||||
Object.defineProperty(screen, 'width', {{
|
||||
get: () => {hw.screen_resolution[0]}
|
||||
}});
|
||||
|
||||
Object.defineProperty(screen, 'height', {{
|
||||
get: () => {hw.screen_resolution[1]}
|
||||
}});
|
||||
|
||||
Object.defineProperty(screen, 'availWidth', {{
|
||||
get: () => {hw.screen_resolution[0]}
|
||||
}});
|
||||
|
||||
Object.defineProperty(screen, 'availHeight', {{
|
||||
get: () => {hw.screen_resolution[1] - 40} // Taskbar
|
||||
}});
|
||||
|
||||
Object.defineProperty(screen, 'colorDepth', {{
|
||||
get: () => {hw.color_depth}
|
||||
}});
|
||||
|
||||
Object.defineProperty(screen, 'pixelDepth', {{
|
||||
get: () => {hw.color_depth}
|
||||
}});
|
||||
|
||||
Object.defineProperty(window, 'devicePixelRatio', {{
|
||||
get: () => {hw.pixel_ratio}
|
||||
}});
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_timezone_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate timezone override script."""
|
||||
return f'''
|
||||
(function() {{
|
||||
const timezone = "{fingerprint.timezone}";
|
||||
const timezoneOffset = {fingerprint.timezone_offset};
|
||||
|
||||
// Override Date.prototype.getTimezoneOffset
|
||||
Date.prototype.getTimezoneOffset = function() {{
|
||||
return timezoneOffset;
|
||||
}};
|
||||
|
||||
// Override Intl.DateTimeFormat
|
||||
const OriginalDateTimeFormat = Intl.DateTimeFormat;
|
||||
Intl.DateTimeFormat = function(...args) {{
|
||||
if (args.length === 0 || !args[1] || !args[1].timeZone) {{
|
||||
if (!args[1]) args[1] = {{}};
|
||||
args[1].timeZone = timezone;
|
||||
}}
|
||||
return new OriginalDateTimeFormat(...args);
|
||||
}};
|
||||
|
||||
// Copy static methods
|
||||
Object.keys(OriginalDateTimeFormat).forEach(key => {{
|
||||
Intl.DateTimeFormat[key] = OriginalDateTimeFormat[key];
|
||||
}});
|
||||
|
||||
// Override resolvedOptions
|
||||
Intl.DateTimeFormat.prototype.resolvedOptions = function() {{
|
||||
const options = OriginalDateTimeFormat.prototype.resolvedOptions.call(this);
|
||||
options.timeZone = timezone;
|
||||
return options;
|
||||
}};
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_audio_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate audio context override script."""
|
||||
return f'''
|
||||
(function() {{
|
||||
const audioParams = {{
|
||||
baseLatency: {fingerprint.audio_context_base_latency},
|
||||
outputLatency: {fingerprint.audio_context_output_latency},
|
||||
sampleRate: {fingerprint.audio_context_sample_rate}
|
||||
}};
|
||||
|
||||
// Override AudioContext
|
||||
const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
|
||||
if (OriginalAudioContext) {{
|
||||
window.AudioContext = function(...args) {{
|
||||
const context = new OriginalAudioContext(...args);
|
||||
|
||||
Object.defineProperty(context, 'baseLatency', {{
|
||||
get: () => audioParams.baseLatency
|
||||
}});
|
||||
|
||||
Object.defineProperty(context, 'outputLatency', {{
|
||||
get: () => audioParams.outputLatency
|
||||
}});
|
||||
|
||||
Object.defineProperty(context, 'sampleRate', {{
|
||||
get: () => audioParams.sampleRate
|
||||
}});
|
||||
|
||||
return context;
|
||||
}};
|
||||
|
||||
// Copy static properties
|
||||
Object.keys(OriginalAudioContext).forEach(key => {{
|
||||
window.AudioContext[key] = OriginalAudioContext[key];
|
||||
}});
|
||||
}}
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_font_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate font detection override script."""
|
||||
fonts_json = json.dumps(fingerprint.font_list)
|
||||
|
||||
return f'''
|
||||
(function() {{
|
||||
const allowedFonts = {fonts_json};
|
||||
|
||||
// Override font detection methods
|
||||
const originalGetComputedStyle = window.getComputedStyle;
|
||||
window.getComputedStyle = function(element, pseudoElt) {{
|
||||
const style = originalGetComputedStyle.apply(this, arguments);
|
||||
const originalPropertyGetter = style.getPropertyValue;
|
||||
|
||||
style.getPropertyValue = function(prop) {{
|
||||
if (prop === 'font-family') {{
|
||||
const value = originalPropertyGetter.apply(this, arguments);
|
||||
// Filter out non-allowed fonts
|
||||
const fonts = value.split(',').map(f => f.trim());
|
||||
const filtered = fonts.filter(f => {{
|
||||
const fontName = f.replace(/['"]/g, '');
|
||||
return allowedFonts.some(allowed =>
|
||||
fontName.toLowerCase().includes(allowed.toLowerCase())
|
||||
);
|
||||
}});
|
||||
return filtered.join(', ');
|
||||
}}
|
||||
return originalPropertyGetter.apply(this, arguments);
|
||||
}};
|
||||
|
||||
return style;
|
||||
}};
|
||||
}})();
|
||||
'''
|
||||
|
||||
def _generate_plugin_script(self, fingerprint: BrowserFingerprint) -> str:
|
||||
"""Generate plugin list override script."""
|
||||
plugins_data = []
|
||||
for plugin in fingerprint.plugins:
|
||||
plugins_data.append({
|
||||
"name": plugin.get("name", ""),
|
||||
"filename": plugin.get("filename", ""),
|
||||
"description": plugin.get("description", ""),
|
||||
"version": plugin.get("version", "")
|
||||
})
|
||||
|
||||
plugins_json = json.dumps(plugins_data)
|
||||
|
||||
return f'''
|
||||
(function() {{
|
||||
const pluginData = {plugins_json};
|
||||
|
||||
// Create fake PluginArray
|
||||
const fakePlugins = {{}};
|
||||
fakePlugins.length = pluginData.length;
|
||||
|
||||
pluginData.forEach((plugin, index) => {{
|
||||
const fakePlugin = {{
|
||||
name: plugin.name,
|
||||
filename: plugin.filename,
|
||||
description: plugin.description,
|
||||
version: plugin.version,
|
||||
length: 1,
|
||||
item: function(index) {{ return this; }},
|
||||
namedItem: function(name) {{ return this; }}
|
||||
}};
|
||||
|
||||
fakePlugins[index] = fakePlugin;
|
||||
fakePlugins[plugin.name] = fakePlugin;
|
||||
}});
|
||||
|
||||
fakePlugins.item = function(index) {{
|
||||
return this[index] || null;
|
||||
}};
|
||||
|
||||
fakePlugins.namedItem = function(name) {{
|
||||
return this[name] || null;
|
||||
}};
|
||||
|
||||
fakePlugins.refresh = function() {{}};
|
||||
|
||||
// Override navigator.plugins
|
||||
Object.defineProperty(navigator, 'plugins', {{
|
||||
get: () => fakePlugins
|
||||
}});
|
||||
}})();
|
||||
'''
|
||||
|
||||
def apply_to_browser_context(self, context: Any, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Apply fingerprint to a Playwright browser context."""
|
||||
# Generate all scripts
|
||||
scripts = self.generate_fingerprint_scripts(fingerprint)
|
||||
|
||||
# Combine all scripts
|
||||
combined_script = '\n'.join(scripts.values())
|
||||
|
||||
# Add script to context
|
||||
context.add_init_script(combined_script)
|
||||
|
||||
# Set viewport
|
||||
context.set_viewport_size({
|
||||
'width': fingerprint.hardware_config.screen_resolution[0],
|
||||
'height': fingerprint.hardware_config.screen_resolution[1]
|
||||
})
|
||||
|
||||
# Set locale
|
||||
context.set_locale(fingerprint.navigator_props.language)
|
||||
|
||||
# Set timezone
|
||||
context.set_timezone_id(fingerprint.timezone)
|
||||
|
||||
# Set user agent
|
||||
context.set_user_agent(fingerprint.navigator_props.user_agent)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren