Initial commit
Dieser Commit ist enthalten in:
868
infrastructure/services/advanced_fingerprint_service.py
Normale Datei
868
infrastructure/services/advanced_fingerprint_service.py
Normale Datei
@ -0,0 +1,868 @@
|
||||
"""
|
||||
Advanced Fingerprint Service - Erweiterte Browser Fingerprinting Implementation
|
||||
"""
|
||||
|
||||
import random
|
||||
import json
|
||||
import logging
|
||||
import hashlib
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
from datetime import datetime, timedelta
|
||||
import uuid
|
||||
|
||||
from domain.services.fingerprint_service import IFingerprintService
|
||||
from domain.entities.browser_fingerprint import (
|
||||
BrowserFingerprint, CanvasNoise, WebRTCConfig,
|
||||
HardwareConfig, NavigatorProperties, StaticComponents
|
||||
)
|
||||
from infrastructure.repositories.fingerprint_repository import FingerprintRepository
|
||||
|
||||
logger = logging.getLogger("advanced_fingerprint_service")
|
||||
|
||||
|
||||
class FingerprintProfiles:
|
||||
"""Vordefinierte realistische Fingerprint-Profile"""
|
||||
|
||||
DESKTOP_PROFILES = [
|
||||
{
|
||||
"name": "Windows Chrome User",
|
||||
"platform": "Win32",
|
||||
"hardware_concurrency": [4, 8, 16],
|
||||
"device_memory": [4, 8, 16],
|
||||
"screen_resolution": [(1920, 1080), (2560, 1440), (1366, 768)],
|
||||
"vendor": "Google Inc.",
|
||||
"renderer": ["ANGLE (Intel HD Graphics)", "ANGLE (NVIDIA GeForce GTX)", "ANGLE (AMD Radeon)"]
|
||||
},
|
||||
{
|
||||
"name": "MacOS Safari User",
|
||||
"platform": "MacIntel",
|
||||
"hardware_concurrency": [4, 8, 12],
|
||||
"device_memory": [8, 16, 32],
|
||||
"screen_resolution": [(1440, 900), (2560, 1600), (5120, 2880)],
|
||||
"vendor": "Apple Inc.",
|
||||
"renderer": ["Apple M1", "Intel Iris", "AMD Radeon Pro"]
|
||||
}
|
||||
]
|
||||
|
||||
MOBILE_PROFILES = [
|
||||
{
|
||||
"name": "Android Chrome",
|
||||
"platform": "Linux armv8l",
|
||||
"hardware_concurrency": [4, 6, 8],
|
||||
"device_memory": [3, 4, 6, 8],
|
||||
"screen_resolution": [(360, 740), (375, 812), (414, 896)],
|
||||
"vendor": "Google Inc.",
|
||||
"renderer": ["Adreno", "Mali", "PowerVR"]
|
||||
},
|
||||
{
|
||||
"name": "iOS Safari",
|
||||
"platform": "iPhone",
|
||||
"hardware_concurrency": [2, 4, 6],
|
||||
"device_memory": [2, 3, 4],
|
||||
"screen_resolution": [(375, 667), (375, 812), (414, 896)],
|
||||
"vendor": "Apple Inc.",
|
||||
"renderer": ["Apple GPU"]
|
||||
}
|
||||
]
|
||||
|
||||
COMMON_FONTS = {
|
||||
"windows": [
|
||||
"Arial", "Arial Black", "Comic Sans MS", "Courier New",
|
||||
"Georgia", "Impact", "Times New Roman", "Trebuchet MS",
|
||||
"Verdana", "Webdings", "Wingdings", "Calibri", "Cambria",
|
||||
"Consolas", "Segoe UI", "Tahoma"
|
||||
],
|
||||
"mac": [
|
||||
"Arial", "Arial Black", "Comic Sans MS", "Courier New",
|
||||
"Georgia", "Helvetica", "Helvetica Neue", "Times New Roman",
|
||||
"Trebuchet MS", "Verdana", "American Typewriter", "Avenir",
|
||||
"Baskerville", "Big Caslon", "Futura", "Geneva", "Gill Sans"
|
||||
],
|
||||
"linux": [
|
||||
"Arial", "Courier New", "Times New Roman", "DejaVu Sans",
|
||||
"DejaVu Serif", "DejaVu Sans Mono", "Liberation Sans",
|
||||
"Liberation Serif", "Ubuntu", "Droid Sans", "Noto Sans"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class AdvancedFingerprintService(IFingerprintService):
|
||||
"""Erweiterte Fingerprint-Service Implementation"""
|
||||
|
||||
def __init__(self, repository: FingerprintRepository = None):
|
||||
self.repository = repository or FingerprintRepository()
|
||||
self.profiles = FingerprintProfiles()
|
||||
self.fingerprint_cache = {}
|
||||
|
||||
def generate_fingerprint(self, profile_type: Optional[str] = None,
|
||||
platform: Optional[str] = None,
|
||||
proxy_location: Optional[str] = None,
|
||||
account_id: Optional[str] = None) -> BrowserFingerprint:
|
||||
"""Generiert einen realistischen Fingerprint"""
|
||||
# Wähle Profil-Typ
|
||||
if profile_type == "mobile":
|
||||
profile = random.choice(self.profiles.MOBILE_PROFILES)
|
||||
else:
|
||||
profile = random.choice(self.profiles.DESKTOP_PROFILES)
|
||||
|
||||
# Canvas Noise Configuration
|
||||
canvas_noise = CanvasNoise(
|
||||
noise_level=random.uniform(0.01, 0.05),
|
||||
seed=random.randint(1000, 9999),
|
||||
algorithm=random.choice(["gaussian", "uniform", "perlin"])
|
||||
)
|
||||
|
||||
# WebRTC Configuration
|
||||
webrtc_config = WebRTCConfig(
|
||||
enabled=random.choice([True, False]),
|
||||
ice_servers=["stun:stun.l.google.com:19302"] if random.random() > 0.5 else [],
|
||||
local_ip_mask=f"10.0.{random.randint(0, 255)}.x",
|
||||
disable_webrtc=random.random() < 0.3 # 30% haben WebRTC deaktiviert
|
||||
)
|
||||
|
||||
# Hardware Configuration
|
||||
hardware_config = HardwareConfig(
|
||||
hardware_concurrency=random.choice(profile["hardware_concurrency"]),
|
||||
device_memory=random.choice(profile["device_memory"]),
|
||||
max_touch_points=10 if "mobile" in profile["name"].lower() else 0,
|
||||
screen_resolution=random.choice(profile["screen_resolution"]),
|
||||
color_depth=random.choice([24, 32]),
|
||||
pixel_ratio=random.choice([1.0, 1.5, 2.0, 3.0])
|
||||
)
|
||||
|
||||
# Navigator Properties
|
||||
languages = self._generate_language_list()
|
||||
navigator_props = NavigatorProperties(
|
||||
platform=profile["platform"],
|
||||
vendor=profile["vendor"],
|
||||
language=languages[0],
|
||||
languages=languages,
|
||||
do_not_track=random.choice(["1", "unspecified", None])
|
||||
)
|
||||
|
||||
# User Agent generieren
|
||||
navigator_props.user_agent = self._generate_user_agent(profile, navigator_props)
|
||||
|
||||
# Font List
|
||||
font_list = self._generate_font_list(profile["platform"])
|
||||
|
||||
# WebGL
|
||||
webgl_vendor = profile["vendor"]
|
||||
webgl_renderer = random.choice(profile["renderer"])
|
||||
|
||||
# Audio Context
|
||||
audio_base_latency = random.uniform(0.00, 0.02)
|
||||
audio_output_latency = random.uniform(0.00, 0.05)
|
||||
audio_sample_rate = random.choice([44100, 48000])
|
||||
|
||||
# Timezone - konsistent mit Proxy-Location
|
||||
timezone, offset = self._get_timezone_for_location(proxy_location)
|
||||
|
||||
# Plugins (nur für Desktop)
|
||||
plugins = []
|
||||
if "mobile" not in profile["name"].lower():
|
||||
plugins = self._generate_plugins()
|
||||
|
||||
# Generate rotation seed for account-bound fingerprints
|
||||
rotation_seed = None
|
||||
if account_id:
|
||||
rotation_seed = hashlib.sha256(f"{account_id}:{datetime.now().strftime('%Y%m')}".encode()).hexdigest()[:16]
|
||||
|
||||
# Create static components for persistence
|
||||
static_components = StaticComponents(
|
||||
device_type="mobile" if "mobile" in profile["name"].lower() else "desktop",
|
||||
os_family=self._get_os_family(profile["platform"]),
|
||||
browser_family="chromium" if "Chrome" in navigator_props.user_agent else "safari",
|
||||
gpu_vendor=webgl_vendor,
|
||||
gpu_model=webgl_renderer,
|
||||
cpu_architecture="arm64" if "arm" in profile["platform"].lower() else "x86_64",
|
||||
base_fonts=font_list[:10], # Store base fonts
|
||||
base_resolution=hardware_config.screen_resolution,
|
||||
base_timezone=timezone
|
||||
)
|
||||
|
||||
fingerprint = BrowserFingerprint(
|
||||
fingerprint_id=str(uuid.uuid4()),
|
||||
canvas_noise=canvas_noise,
|
||||
webrtc_config=webrtc_config,
|
||||
font_list=font_list,
|
||||
hardware_config=hardware_config,
|
||||
navigator_props=navigator_props,
|
||||
webgl_vendor=webgl_vendor,
|
||||
webgl_renderer=webgl_renderer,
|
||||
audio_context_base_latency=audio_base_latency,
|
||||
audio_context_output_latency=audio_output_latency,
|
||||
audio_context_sample_rate=audio_sample_rate,
|
||||
timezone=timezone,
|
||||
timezone_offset=offset,
|
||||
plugins=plugins,
|
||||
created_at=datetime.now(),
|
||||
# New persistence fields
|
||||
static_components=static_components if account_id else None,
|
||||
rotation_seed=rotation_seed,
|
||||
account_bound=bool(account_id)
|
||||
)
|
||||
|
||||
# Speichere in Repository
|
||||
self.repository.save(fingerprint)
|
||||
|
||||
# Cache für schnellen Zugriff
|
||||
self.fingerprint_cache[fingerprint.fingerprint_id] = fingerprint
|
||||
|
||||
logger.info(f"Generated new fingerprint: {fingerprint.fingerprint_id}")
|
||||
return fingerprint
|
||||
|
||||
def _get_os_family(self, platform: str) -> str:
|
||||
"""Determine OS family from platform string"""
|
||||
if "Win" in platform:
|
||||
return "windows"
|
||||
elif "Mac" in platform or "iPhone" in platform:
|
||||
return "macos" if "Mac" in platform else "ios"
|
||||
elif "Android" in platform or "Linux" in platform:
|
||||
return "android" if "Android" in platform else "linux"
|
||||
return "unknown"
|
||||
|
||||
def _generate_language_list(self) -> List[str]:
|
||||
"""Generiert realistische Sprachliste"""
|
||||
language_sets = [
|
||||
["de-DE", "de", "en-US", "en"],
|
||||
["en-US", "en"],
|
||||
["en-GB", "en-US", "en"],
|
||||
["fr-FR", "fr", "en-US", "en"],
|
||||
["es-ES", "es", "en-US", "en"],
|
||||
["de-DE", "de", "en-GB", "en"]
|
||||
]
|
||||
return random.choice(language_sets)
|
||||
|
||||
def _generate_user_agent(self, profile: Dict, nav_props: NavigatorProperties) -> str:
|
||||
"""Generiert realistischen User Agent"""
|
||||
chrome_version = random.randint(96, 120)
|
||||
|
||||
if "Windows" in profile["name"]:
|
||||
return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{chrome_version}.0.0.0 Safari/537.36"
|
||||
elif "Mac" in profile["name"]:
|
||||
return f"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{chrome_version}.0.0.0 Safari/537.36"
|
||||
elif "Android" in profile["name"]:
|
||||
android_version = random.randint(10, 13)
|
||||
return f"Mozilla/5.0 (Linux; Android {android_version}; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{chrome_version}.0.0.0 Mobile Safari/537.36"
|
||||
elif "iOS" in profile["name"]:
|
||||
ios_version = random.randint(14, 16)
|
||||
return f"Mozilla/5.0 (iPhone; CPU iPhone OS {ios_version}_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/{ios_version}.0 Mobile/15E148 Safari/604.1"
|
||||
|
||||
return f"Mozilla/5.0 (compatible; Unknown)"
|
||||
|
||||
def _generate_font_list(self, platform: str) -> List[str]:
|
||||
"""Generiert plattform-spezifische Fontliste"""
|
||||
if "Win" in platform:
|
||||
fonts = self.profiles.COMMON_FONTS["windows"]
|
||||
elif "Mac" in platform or "iPhone" in platform:
|
||||
fonts = self.profiles.COMMON_FONTS["mac"]
|
||||
else:
|
||||
fonts = self.profiles.COMMON_FONTS["linux"]
|
||||
|
||||
# Zufällige Auswahl (60-90% der Fonts)
|
||||
num_fonts = random.randint(int(len(fonts) * 0.6), int(len(fonts) * 0.9))
|
||||
return random.sample(fonts, num_fonts)
|
||||
|
||||
def _generate_plugins(self) -> List[Dict[str, str]]:
|
||||
"""Generiert Plugin-Liste für Desktop"""
|
||||
all_plugins = [
|
||||
{"name": "Chrome PDF Plugin", "filename": "internal-pdf-viewer"},
|
||||
{"name": "Chrome PDF Viewer", "filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai"},
|
||||
{"name": "Native Client", "filename": "internal-nacl-plugin"},
|
||||
{"name": "Shockwave Flash", "filename": "pepperflashplugin.dll"}
|
||||
]
|
||||
|
||||
# 0-3 Plugins
|
||||
num_plugins = random.randint(0, 3)
|
||||
return random.sample(all_plugins, num_plugins)
|
||||
|
||||
def rotate_fingerprint(self, current: BrowserFingerprint,
|
||||
rotation_strategy: str = "gradual") -> BrowserFingerprint:
|
||||
"""Rotiert einen Fingerprint"""
|
||||
if rotation_strategy == "complete":
|
||||
# Komplett neuer Fingerprint
|
||||
return self.generate_fingerprint()
|
||||
|
||||
elif rotation_strategy == "minimal":
|
||||
# Nur kleine Änderungen
|
||||
new_fingerprint = BrowserFingerprint(
|
||||
fingerprint_id=str(uuid.uuid4()),
|
||||
canvas_noise=CanvasNoise(
|
||||
noise_level=current.canvas_noise.noise_level + random.uniform(-0.01, 0.01),
|
||||
seed=random.randint(1000, 9999),
|
||||
algorithm=current.canvas_noise.algorithm
|
||||
),
|
||||
webrtc_config=current.webrtc_config,
|
||||
font_list=current.font_list,
|
||||
hardware_config=current.hardware_config,
|
||||
navigator_props=current.navigator_props,
|
||||
webgl_vendor=current.webgl_vendor,
|
||||
webgl_renderer=current.webgl_renderer,
|
||||
audio_context_base_latency=current.audio_context_base_latency + random.uniform(-0.001, 0.001),
|
||||
audio_context_output_latency=current.audio_context_output_latency,
|
||||
audio_context_sample_rate=current.audio_context_sample_rate,
|
||||
timezone=current.timezone,
|
||||
timezone_offset=current.timezone_offset,
|
||||
plugins=current.plugins,
|
||||
created_at=datetime.now(),
|
||||
last_rotated=datetime.now()
|
||||
)
|
||||
|
||||
else: # gradual
|
||||
# Moderate Änderungen
|
||||
new_fingerprint = BrowserFingerprint(
|
||||
fingerprint_id=str(uuid.uuid4()),
|
||||
canvas_noise=CanvasNoise(
|
||||
noise_level=random.uniform(0.01, 0.05),
|
||||
seed=random.randint(1000, 9999),
|
||||
algorithm=random.choice(["gaussian", "uniform", "perlin"])
|
||||
),
|
||||
webrtc_config=WebRTCConfig(
|
||||
enabled=current.webrtc_config.enabled,
|
||||
ice_servers=current.webrtc_config.ice_servers,
|
||||
local_ip_mask=f"10.0.{random.randint(0, 255)}.x",
|
||||
disable_webrtc=current.webrtc_config.disable_webrtc
|
||||
),
|
||||
font_list=self._slightly_modify_fonts(current.font_list),
|
||||
hardware_config=current.hardware_config,
|
||||
navigator_props=current.navigator_props,
|
||||
webgl_vendor=current.webgl_vendor,
|
||||
webgl_renderer=current.webgl_renderer,
|
||||
audio_context_base_latency=random.uniform(0.00, 0.02),
|
||||
audio_context_output_latency=random.uniform(0.00, 0.05),
|
||||
audio_context_sample_rate=current.audio_context_sample_rate,
|
||||
timezone=current.timezone,
|
||||
timezone_offset=current.timezone_offset,
|
||||
plugins=current.plugins,
|
||||
created_at=current.created_at,
|
||||
last_rotated=datetime.now()
|
||||
)
|
||||
|
||||
# Update last_rotated für alten Fingerprint
|
||||
self.repository.update_last_rotated(current.fingerprint_id, datetime.now())
|
||||
|
||||
# Speichere neuen Fingerprint
|
||||
self.repository.save(new_fingerprint)
|
||||
self.fingerprint_cache[new_fingerprint.fingerprint_id] = new_fingerprint
|
||||
|
||||
logger.info(f"Rotated fingerprint {current.fingerprint_id} -> {new_fingerprint.fingerprint_id} (strategy: {rotation_strategy})")
|
||||
return new_fingerprint
|
||||
|
||||
def _slightly_modify_fonts(self, fonts: List[str]) -> List[str]:
|
||||
"""Modifiziert Fontliste leicht"""
|
||||
new_fonts = fonts.copy()
|
||||
|
||||
# Füge 1-2 Fonts hinzu oder entferne
|
||||
if random.random() > 0.5 and len(new_fonts) > 5:
|
||||
# Entferne 1-2 Fonts
|
||||
for _ in range(random.randint(1, 2)):
|
||||
if new_fonts:
|
||||
new_fonts.pop(random.randint(0, len(new_fonts) - 1))
|
||||
else:
|
||||
# Füge 1-2 Fonts hinzu
|
||||
additional_fonts = ["Consolas", "Monaco", "Menlo", "Ubuntu Mono"]
|
||||
for font in random.sample(additional_fonts, min(2, len(additional_fonts))):
|
||||
if font not in new_fonts:
|
||||
new_fonts.append(font)
|
||||
|
||||
return new_fonts
|
||||
|
||||
def validate_fingerprint(self, fingerprint: BrowserFingerprint) -> Tuple[bool, List[str]]:
|
||||
"""Validiert einen Fingerprint"""
|
||||
issues = []
|
||||
|
||||
# Hardware Konsistenz
|
||||
if fingerprint.hardware_config.hardware_concurrency > fingerprint.hardware_config.device_memory * 2:
|
||||
issues.append("Hardware concurrency zu hoch für device memory")
|
||||
|
||||
# Platform Konsistenz
|
||||
if "Win" in fingerprint.navigator_props.platform and "Mac" in fingerprint.webgl_renderer:
|
||||
issues.append("Windows platform mit Mac renderer inkonsistent")
|
||||
|
||||
# Mobile Konsistenz
|
||||
is_mobile = "iPhone" in fingerprint.navigator_props.platform or "Android" in fingerprint.navigator_props.user_agent
|
||||
if is_mobile and fingerprint.hardware_config.max_touch_points == 0:
|
||||
issues.append("Mobile device ohne touch points")
|
||||
|
||||
# Font Konsistenz
|
||||
if len(fingerprint.font_list) < 5:
|
||||
issues.append("Zu wenige Fonts für realistisches Profil")
|
||||
|
||||
# WebRTC Konsistenz
|
||||
if fingerprint.webrtc_config.disable_webrtc and fingerprint.webrtc_config.ice_servers:
|
||||
issues.append("WebRTC deaktiviert aber ICE servers konfiguriert")
|
||||
|
||||
return len(issues) == 0, issues
|
||||
|
||||
def save_fingerprint(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Speichert einen Fingerprint"""
|
||||
self.repository.save(fingerprint)
|
||||
self.fingerprint_cache[fingerprint.fingerprint_id] = fingerprint
|
||||
|
||||
def load_fingerprint(self, fingerprint_id: str) -> Optional[BrowserFingerprint]:
|
||||
"""Lädt einen Fingerprint"""
|
||||
# Check cache first
|
||||
if fingerprint_id in self.fingerprint_cache:
|
||||
return self.fingerprint_cache[fingerprint_id]
|
||||
|
||||
# Load from repository
|
||||
fingerprint = self.repository.find_by_id(fingerprint_id)
|
||||
if fingerprint:
|
||||
self.fingerprint_cache[fingerprint_id] = fingerprint
|
||||
|
||||
return fingerprint
|
||||
|
||||
def get_fingerprint_pool(self, count: int = 10,
|
||||
platform: Optional[str] = None) -> List[BrowserFingerprint]:
|
||||
"""Holt einen Pool von Fingerprints"""
|
||||
# Hole existierende Fingerprints
|
||||
existing = self.repository.get_random_fingerprints(count // 2)
|
||||
|
||||
# Generiere neue für Diversität
|
||||
new_count = count - len(existing)
|
||||
new_fingerprints = []
|
||||
for _ in range(new_count):
|
||||
fp = self.generate_fingerprint(platform=platform)
|
||||
new_fingerprints.append(fp)
|
||||
|
||||
return existing + new_fingerprints
|
||||
|
||||
def _get_timezone_for_location(self, proxy_location: Optional[str] = None) -> Tuple[str, int]:
|
||||
"""Gibt Timezone basierend auf Proxy-Location zurück"""
|
||||
# Location-basierte Timezones
|
||||
location_timezones = {
|
||||
# Deutschland
|
||||
"DE": ("Europe/Berlin", -60), # UTC+1
|
||||
"de": ("Europe/Berlin", -60),
|
||||
"germany": ("Europe/Berlin", -60),
|
||||
"berlin": ("Europe/Berlin", -60),
|
||||
"frankfurt": ("Europe/Berlin", -60),
|
||||
"munich": ("Europe/Berlin", -60),
|
||||
|
||||
# UK
|
||||
"GB": ("Europe/London", 0), # UTC+0
|
||||
"gb": ("Europe/London", 0),
|
||||
"uk": ("Europe/London", 0),
|
||||
"london": ("Europe/London", 0),
|
||||
|
||||
# Frankreich
|
||||
"FR": ("Europe/Paris", -60), # UTC+1
|
||||
"fr": ("Europe/Paris", -60),
|
||||
"france": ("Europe/Paris", -60),
|
||||
"paris": ("Europe/Paris", -60),
|
||||
|
||||
# USA Ostküste
|
||||
"US-NY": ("America/New_York", 300), # UTC-5
|
||||
"us-east": ("America/New_York", 300),
|
||||
"new york": ("America/New_York", 300),
|
||||
"newyork": ("America/New_York", 300),
|
||||
|
||||
# USA Westküste
|
||||
"US-CA": ("America/Los_Angeles", 480), # UTC-8
|
||||
"us-west": ("America/Los_Angeles", 480),
|
||||
"los angeles": ("America/Los_Angeles", 480),
|
||||
"california": ("America/Los_Angeles", 480),
|
||||
|
||||
# Spanien
|
||||
"ES": ("Europe/Madrid", -60), # UTC+1
|
||||
"es": ("Europe/Madrid", -60),
|
||||
"spain": ("Europe/Madrid", -60),
|
||||
"madrid": ("Europe/Madrid", -60),
|
||||
|
||||
# Italien
|
||||
"IT": ("Europe/Rome", -60), # UTC+1
|
||||
"it": ("Europe/Rome", -60),
|
||||
"italy": ("Europe/Rome", -60),
|
||||
"rome": ("Europe/Rome", -60),
|
||||
|
||||
# Niederlande
|
||||
"NL": ("Europe/Amsterdam", -60), # UTC+1
|
||||
"nl": ("Europe/Amsterdam", -60),
|
||||
"netherlands": ("Europe/Amsterdam", -60),
|
||||
"amsterdam": ("Europe/Amsterdam", -60),
|
||||
|
||||
# Kanada
|
||||
"CA": ("America/Toronto", 300), # UTC-5
|
||||
"ca": ("America/Toronto", 300),
|
||||
"canada": ("America/Toronto", 300),
|
||||
"toronto": ("America/Toronto", 300),
|
||||
|
||||
# Australien
|
||||
"AU": ("Australia/Sydney", -660), # UTC+11
|
||||
"au": ("Australia/Sydney", -660),
|
||||
"australia": ("Australia/Sydney", -660),
|
||||
"sydney": ("Australia/Sydney", -660),
|
||||
}
|
||||
|
||||
# Wenn Location angegeben, verwende passende Timezone
|
||||
if proxy_location:
|
||||
# Normalisiere Location (lowercase, entferne Leerzeichen)
|
||||
normalized_location = proxy_location.lower().strip()
|
||||
|
||||
# Suche in Location-Map
|
||||
for key, timezone_data in location_timezones.items():
|
||||
if key.lower() in normalized_location or normalized_location in key.lower():
|
||||
logger.info(f"Using timezone {timezone_data[0]} for location '{proxy_location}'")
|
||||
return timezone_data
|
||||
|
||||
# Fallback: Zufällige Timezone aus häufig genutzten
|
||||
common_timezones = [
|
||||
("Europe/Berlin", -60),
|
||||
("Europe/London", 0),
|
||||
("Europe/Paris", -60),
|
||||
("America/New_York", 300),
|
||||
("America/Los_Angeles", 480),
|
||||
("Europe/Madrid", -60),
|
||||
("America/Toronto", 300)
|
||||
]
|
||||
|
||||
timezone_data = random.choice(common_timezones)
|
||||
logger.info(f"Using random timezone {timezone_data[0]} (no location match for '{proxy_location}')")
|
||||
return timezone_data
|
||||
|
||||
def apply_fingerprint(self, browser_context: Any, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Wendet Fingerprint auf Browser Context an"""
|
||||
# Diese Methode würde JavaScript injection und Browser-Konfiguration durchführen
|
||||
# Beispiel-Implementation für Playwright:
|
||||
|
||||
if hasattr(browser_context, 'add_init_script'):
|
||||
# Canvas Noise Injection
|
||||
canvas_script = self._generate_canvas_noise_script(fingerprint.canvas_noise)
|
||||
browser_context.add_init_script(canvas_script)
|
||||
|
||||
# WebRTC Protection
|
||||
if fingerprint.webrtc_config.disable_webrtc:
|
||||
webrtc_script = self._generate_webrtc_block_script()
|
||||
browser_context.add_init_script(webrtc_script)
|
||||
|
||||
# Navigator Override
|
||||
navigator_script = self._generate_navigator_override_script(fingerprint.navigator_props)
|
||||
browser_context.add_init_script(navigator_script)
|
||||
|
||||
# Hardware Override
|
||||
hardware_script = self._generate_hardware_override_script(fingerprint.hardware_config)
|
||||
browser_context.add_init_script(hardware_script)
|
||||
|
||||
logger.info(f"Applied fingerprint {fingerprint.fingerprint_id} to browser context")
|
||||
|
||||
def _generate_canvas_noise_script(self, canvas_noise: CanvasNoise) -> str:
|
||||
"""Generiert Canvas Noise Injection Script"""
|
||||
return f"""
|
||||
(function() {{
|
||||
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
|
||||
const noiseLevel = {canvas_noise.noise_level};
|
||||
const seed = {canvas_noise.seed};
|
||||
|
||||
CanvasRenderingContext2D.prototype.getImageData = function() {{
|
||||
const imageData = originalGetImageData.apply(this, arguments);
|
||||
|
||||
// Add noise to image data
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {{
|
||||
imageData.data[i] += Math.random() * noiseLevel * 255;
|
||||
imageData.data[i+1] += Math.random() * noiseLevel * 255;
|
||||
imageData.data[i+2] += Math.random() * noiseLevel * 255;
|
||||
}}
|
||||
|
||||
return imageData;
|
||||
}};
|
||||
}})();
|
||||
"""
|
||||
|
||||
def _generate_webrtc_block_script(self) -> str:
|
||||
"""Generiert erweiterten WebRTC Block Script mit IP Leak Prevention"""
|
||||
return """
|
||||
(function() {
|
||||
// Erweiterte WebRTC Leak Prevention
|
||||
|
||||
// 1. Basis WebRTC Blocking
|
||||
const OriginalRTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
|
||||
|
||||
if (OriginalRTCPeerConnection) {
|
||||
// Override RTCPeerConnection
|
||||
window.RTCPeerConnection = function(config, constraints) {
|
||||
// Filtere ICE Server wenn gewünscht
|
||||
if (config && config.iceServers) {
|
||||
config.iceServers = config.iceServers.filter(server => {
|
||||
// Entferne STUN Server die IP leaken könnten
|
||||
if (server.urls) {
|
||||
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
||||
return urls.every(url => !url.includes('stun:'));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const pc = new OriginalRTCPeerConnection(config, constraints);
|
||||
|
||||
// Override onicecandidate
|
||||
const originalOnIceCandidate = pc.onicecandidate;
|
||||
Object.defineProperty(pc, 'onicecandidate', {
|
||||
get: function() {
|
||||
return originalOnIceCandidate;
|
||||
},
|
||||
set: function(func) {
|
||||
originalOnIceCandidate = function(event) {
|
||||
if (event.candidate) {
|
||||
// Filtere lokale IP Adressen
|
||||
const candidateStr = event.candidate.candidate;
|
||||
|
||||
// Regex für private IPs
|
||||
const privateIPRegex = /(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|[a-f0-9:]+::/gi);
|
||||
|
||||
// Wenn private IP gefunden, modifiziere Kandidat
|
||||
if (privateIPRegex.test(candidateStr)) {
|
||||
const modifiedCandidate = candidateStr.replace(privateIPRegex, '10.0.0.x');
|
||||
event.candidate.candidate = modifiedCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (func) {
|
||||
func(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Override createDataChannel
|
||||
const originalCreateDataChannel = pc.createDataChannel;
|
||||
pc.createDataChannel = function(label, options) {
|
||||
// Log für Debugging aber blockiere nicht
|
||||
console.debug('DataChannel created:', label);
|
||||
return originalCreateDataChannel.call(this, label, options);
|
||||
};
|
||||
|
||||
// Override getStats für Fingerprinting Protection
|
||||
const originalGetStats = pc.getStats;
|
||||
pc.getStats = function() {
|
||||
return originalGetStats.call(this).then(stats => {
|
||||
// Modifiziere Stats um Fingerprinting zu erschweren
|
||||
stats.forEach(stat => {
|
||||
if (stat.type === 'candidate-pair') {
|
||||
// Verstecke echte RTT
|
||||
if (stat.currentRoundTripTime) {
|
||||
stat.currentRoundTripTime = Math.random() * 0.1 + 0.05;
|
||||
}
|
||||
}
|
||||
});
|
||||
return stats;
|
||||
});
|
||||
};
|
||||
|
||||
return pc;
|
||||
};
|
||||
|
||||
// Kopiere statische Eigenschaften
|
||||
window.RTCPeerConnection.prototype = OriginalRTCPeerConnection.prototype;
|
||||
window.RTCPeerConnection.generateCertificate = OriginalRTCPeerConnection.generateCertificate;
|
||||
|
||||
// Aliase für andere Browser
|
||||
if (window.webkitRTCPeerConnection) {
|
||||
window.webkitRTCPeerConnection = window.RTCPeerConnection;
|
||||
}
|
||||
if (window.mozRTCPeerConnection) {
|
||||
window.mozRTCPeerConnection = window.RTCPeerConnection;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. MediaDevices Protection
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
||||
const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices;
|
||||
navigator.mediaDevices.enumerateDevices = function() {
|
||||
return originalEnumerateDevices.call(this).then(devices => {
|
||||
// Randomisiere Device IDs
|
||||
return devices.map(device => {
|
||||
return {
|
||||
...device,
|
||||
deviceId: device.deviceId ? btoa(Math.random().toString()).substring(0, 20) : '',
|
||||
groupId: device.groupId ? btoa(Math.random().toString()).substring(0, 20) : ''
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Block WebRTC komplett wenn gewünscht
|
||||
if (window.__BLOCK_WEBRTC_COMPLETELY__) {
|
||||
delete window.RTCPeerConnection;
|
||||
delete window.webkitRTCPeerConnection;
|
||||
delete window.mozRTCPeerConnection;
|
||||
delete window.RTCSessionDescription;
|
||||
delete window.RTCIceCandidate;
|
||||
delete window.MediaStream;
|
||||
delete window.MediaStreamTrack;
|
||||
}
|
||||
})();
|
||||
"""
|
||||
|
||||
def _generate_navigator_override_script(self, nav_props: NavigatorProperties) -> str:
|
||||
"""Generiert Navigator Override Script"""
|
||||
return f"""
|
||||
(function() {{
|
||||
Object.defineProperty(navigator, 'platform', {{
|
||||
get: () => '{nav_props.platform}'
|
||||
}});
|
||||
Object.defineProperty(navigator, 'vendor', {{
|
||||
get: () => '{nav_props.vendor}'
|
||||
}});
|
||||
Object.defineProperty(navigator, 'language', {{
|
||||
get: () => '{nav_props.language}'
|
||||
}});
|
||||
Object.defineProperty(navigator, 'languages', {{
|
||||
get: () => {json.dumps(nav_props.languages)}
|
||||
}});
|
||||
}})();
|
||||
"""
|
||||
|
||||
def _generate_hardware_override_script(self, hw_config: HardwareConfig) -> str:
|
||||
"""Generiert Hardware Override Script"""
|
||||
return f"""
|
||||
(function() {{
|
||||
Object.defineProperty(navigator, 'hardwareConcurrency', {{
|
||||
get: () => {hw_config.hardware_concurrency}
|
||||
}});
|
||||
Object.defineProperty(navigator, 'deviceMemory', {{
|
||||
get: () => {hw_config.device_memory}
|
||||
}});
|
||||
Object.defineProperty(navigator, 'maxTouchPoints', {{
|
||||
get: () => {hw_config.max_touch_points}
|
||||
}});
|
||||
}})();
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def get_fingerprint_score(self, fingerprint: BrowserFingerprint) -> float:
|
||||
"""Bewertet Fingerprint-Qualität"""
|
||||
score = 1.0
|
||||
|
||||
# Validierung
|
||||
valid, issues = self.validate_fingerprint(fingerprint)
|
||||
if not valid:
|
||||
score -= 0.1 * len(issues)
|
||||
|
||||
# Alter des Fingerprints
|
||||
age = datetime.now() - fingerprint.created_at
|
||||
if age > timedelta(days=7):
|
||||
score -= 0.2
|
||||
elif age > timedelta(days=30):
|
||||
score -= 0.4
|
||||
|
||||
# Rotation
|
||||
if fingerprint.last_rotated:
|
||||
time_since_rotation = datetime.now() - fingerprint.last_rotated
|
||||
if time_since_rotation < timedelta(hours=1):
|
||||
score -= 0.3 # Zu häufige Rotation
|
||||
|
||||
# Font-Diversität
|
||||
if len(fingerprint.font_list) < 10:
|
||||
score -= 0.1
|
||||
elif len(fingerprint.font_list) > 50:
|
||||
score -= 0.1 # Zu viele Fonts unrealistisch
|
||||
|
||||
return max(0.0, min(1.0, score))
|
||||
|
||||
|
||||
def create_account_fingerprint(self, account_id: str,
|
||||
profile_type: Optional[str] = None,
|
||||
platform: Optional[str] = None,
|
||||
proxy_location: Optional[str] = None) -> BrowserFingerprint:
|
||||
"""Creates a new fingerprint bound to a specific account"""
|
||||
fingerprint = self.generate_fingerprint(
|
||||
profile_type=profile_type,
|
||||
platform=platform,
|
||||
proxy_location=proxy_location,
|
||||
account_id=account_id
|
||||
)
|
||||
|
||||
# Link fingerprint to account
|
||||
self.repository.link_to_account(fingerprint.fingerprint_id, account_id)
|
||||
|
||||
return fingerprint
|
||||
|
||||
def get_account_fingerprint(self, account_id: str) -> Optional[BrowserFingerprint]:
|
||||
"""Get the primary fingerprint for an account"""
|
||||
try:
|
||||
fingerprint_id = self.repository.get_primary_fingerprint_for_account(account_id)
|
||||
if fingerprint_id:
|
||||
logger.debug(f"Found fingerprint {fingerprint_id} for account {account_id}")
|
||||
return self.load_fingerprint(fingerprint_id)
|
||||
else:
|
||||
logger.debug(f"No fingerprint found for account {account_id}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting fingerprint for account {account_id}: {e}")
|
||||
return None
|
||||
|
||||
def load_for_session(self, fingerprint_id: str,
|
||||
date_str: Optional[str] = None) -> BrowserFingerprint:
|
||||
"""Load fingerprint for a session with deterministic daily variations"""
|
||||
try:
|
||||
fingerprint = self.load_fingerprint(fingerprint_id)
|
||||
if not fingerprint:
|
||||
logger.error(f"Fingerprint {fingerprint_id} not found in repository")
|
||||
raise ValueError(f"Fingerprint {fingerprint_id} not found")
|
||||
|
||||
logger.debug(f"Loading fingerprint {fingerprint_id} for session")
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading fingerprint {fingerprint_id}: {e}")
|
||||
raise
|
||||
|
||||
if not fingerprint.rotation_seed:
|
||||
# No seed means no deterministic variation
|
||||
return fingerprint
|
||||
|
||||
# Apply deterministic variations based on date
|
||||
if date_str is None:
|
||||
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# Create a copy with daily variations
|
||||
session_fingerprint = BrowserFingerprint.from_dict(fingerprint.to_dict())
|
||||
|
||||
# Apply deterministic noise to canvas seed
|
||||
hash_input = f"{fingerprint.rotation_seed}:canvas:{date_str}"
|
||||
canvas_seed = int(hashlib.sha256(hash_input.encode()).hexdigest()[:8], 16) % 1000000
|
||||
session_fingerprint.canvas_noise.seed = canvas_seed
|
||||
|
||||
# Slight audio variations
|
||||
hash_input = f"{fingerprint.rotation_seed}:audio:{date_str}"
|
||||
audio_var = int(hashlib.sha256(hash_input.encode()).hexdigest()[:8], 16) / 0xFFFFFFFF
|
||||
session_fingerprint.audio_context_base_latency += (audio_var - 0.5) * 0.002
|
||||
|
||||
return session_fingerprint
|
||||
|
||||
|
||||
|
||||
def update_fingerprint_stats(self, fingerprint_id: str, account_id: str, success: bool) -> None:
|
||||
"""Update fingerprint usage statistics for an account"""
|
||||
self.repository.update_fingerprint_stats(fingerprint_id, account_id, success)
|
||||
|
||||
def cleanup_old_fingerprints(self, older_than: datetime) -> int:
|
||||
"""Bereinigt alte Fingerprints - Dummy implementation"""
|
||||
# Removed functionality, just return 0
|
||||
return 0
|
||||
|
||||
def detect_fingerprinting(self, page_content: str) -> Dict[str, Any]:
|
||||
"""Erkennt Fingerprinting-Versuche - Dummy implementation"""
|
||||
# Removed functionality, return empty detection
|
||||
return {
|
||||
"canvas": False,
|
||||
"webrtc": False,
|
||||
"fonts": False,
|
||||
"audio": False,
|
||||
"webgl": False,
|
||||
"hardware": False,
|
||||
"techniques": [],
|
||||
"total_techniques": 0,
|
||||
"risk_level": "none"
|
||||
}
|
||||
|
||||
def get_fingerprint_pool(self, count: int = 10,
|
||||
platform: Optional[str] = None) -> List[BrowserFingerprint]:
|
||||
"""Holt einen Pool von Fingerprints - Simple implementation"""
|
||||
# Just generate new fingerprints
|
||||
fingerprints = []
|
||||
for _ in range(count):
|
||||
fp = self.generate_fingerprint(platform=platform)
|
||||
fingerprints.append(fp)
|
||||
return fingerprints
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren