In this Article
بصمة WebGL تقنية تعريف تستخدمها أنظمة مكافحة الروبوتات على نطاق واسع لاكتشاف الأنشطة المؤتمتة. ولأنها تستفيد من إشارات على مستوى العتاد، يصعب التعامل معها إلى حد كبير، ما يضيف تحدياً جديداً لمن يعتمدون على تجريف الويب لأغراض تجارية. ومع ذلك، توجد طرق للحفاظ على بصمة منخفضة وجمع البيانات، تابع القراءة لمعرفة المزيد.
ما WebGL؟ شرح مفصل
يرمز WebGL إلى مكتبة رسومات الويب، وهي API لـ JavaScript. تستخدمه المتصفحات لعرض الرسومات الديناميكية ثنائية وثلاثية الأبعاد 2D و3D دون الحاجة إلى تثبيت إضافات. ولإنجاز مهام العرض هذه، تعتمد API بدرجة كبيرة على GPU، أي وحدة معالجة الرسومات. وتستخدم مواقع الويب WebGL بكثافة حالياً للتأثيرات المرئية والألعاب والواجهات الرسومية المعقدة. وفي الواقع، يكتسب WebGL 2 انتشاراً متزايداً لأنه يقدم مزيداً من الميزات. وتدعم المتصفحات الحديثة مثل Chrome وEdge وSafari WebGL 2 بنسبة 95-99%، فيما لا تزال المتصفحات الأقدم والمتصفحات عديمة الواجهة تفتقر إليه.
بما أن مئات وحدات GPU متاحة، بل إن الطرازات من الشركة المصنعة نفسها تختلف اختلافاً كبيراً، فستُعرض الصورة ذاتها بطرق مختلفة. كما يؤثر المتصفح وبرامج التشغيل المثبتة وحتى اختلافات العتاد، مثل كيفية توصيل GPU باللوحة الأم، في النتيجة. يشبه الأمر زيارة متجر للتقنيات المنزلية ورؤية عشرات أجهزة التلفاز المعروضة، وكلها تعرض الإعلان نفسه، لكن الصور تختلف قليلاً. وقد صار ذلك أساساً لبصمة WebGL. لاحظ أن بصمة WebGL لا تحدث إلا عند استخدام JavaScript. فلا يمكن لصفحة HTML ثابتة تنفيذها.
بصمة WebGL: نظرة عامة خطوة بخطوة
بصورة تقريبية، تبدو العملية كاملة كالتالي:
- ينشئ موقع ويب عنصراً <canvas> مخفياً في HTML الصفحة. ويهيئ بيئة عرض WebGL.
- يستخدم JavaScript استدعاءات gl.getParameter() لاستخراج خصائص GPU، مثل المورد والطراز.
- يطلب برنامج نصي للبصمة من المتصفح عرض صورة اختبارية، ويستخدم readPixels() لاستخراج بيانات البكسل.
يمكنك زيارة BrowserLeaks، ثم النقر على تقرير WebGL لرؤية بصمتك الفريدة. وتبدو هكذا:
فيما يلي مثال لبرنامج نصي تستخدمه مواقع الويب. ويمكنك أيضاً استخدامه لأغراض الاختبار، مثل التشخيص وتقارير الأخطاء واختبار التوافق وغير ذلك. تذكر تشغيل البرنامج النصي على أجهزة تملكها أو بعد الحصول على موافقة صريحة حصراً. تجنب استخدام البرنامج النصي للتتبع أو كشف الهوية. تعامل مع النتائج بوصفها بيانات حساسة ولا تشاركها مع أطراف ثالثة إلا بإذن. لا تتحمل DataImpulse مسؤولية الاستخدام غير القانوني للبرنامج النصي.
الخطوة 1. تشغيل بيئة WebGL
// Lightweight FNV-1a hash for strings/byte arrays (synchronous, simple)
function fnv1aHashFromBytes(bytes) {
let h = 0x811c9dc5 >>> 0;
for (let i = 0; i < bytes.length; i++) {
h ^= bytes[i];
// multiply by FNV prime (mod 2^32)
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
}
return ('00000000' + (h >>> 0).toString(16)).slice(-8);
}
function fnv1aHashFromString(s) {
const bytes = new Uint8Array(s.length);
for (let i = 0; i < s.length; i++) bytes[i] = s.charCodeAt(i) & 0xff;
return fnv1aHashFromBytes(bytes);
}
// Create an (optionally hidden) canvas and return WebGLRenderingContext / WebGL2RenderingContext
function createWebGLContext(opts = {webgl2Preferred: true, preserveDrawingBuffer: false}) {
const canvas = document.createElement('canvas');
canvas.width = 256; canvas.height = 256;
// optionally keep canvas off-DOM so it stays invisible
canvas.style.display = 'none';
document.body && document.body.appendChild(canvas); // append only if available
let gl = null;
if (opts.webgl2Preferred) {
try { gl = canvas.getContext('webgl2', {preserveDrawingBuffer: opts.preserveDrawingBuffer}); } catch (e) {}
}
if (!gl) {
try { gl = canvas.getContext('webgl', {preserveDrawingBuffer: opts.preserveDrawingBuffer}) || canvas.getContext('experimental-webgl'); } catch (e) {}
}
return {gl, canvas};
}
ينشئ هذا الجزء لوحة رسم غير مرئية تعمل في الخلفية أثناء التصفح ويحدد دقتها. وإذا لم يكن WebGL 2 مدعوماً، فسيعود بسلاسة إلى WebGL.
الخطوة 2. الحصول على معلومات العتاد
function getWebGLHardwareInfo(gl) {
const info = {};
// Standard parameters
info.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
info.maxCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
info.maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
info.maxVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
info.maxFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
info.shaderPrecision = {
vertexHighFloat: gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT),
fragmentHighFloat: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT)
};
// Try to get unmasked renderer/vendor (if browser exposes WEBGL_debug_renderer_info)
const dbg = gl.getExtension('WEBGL_debug_renderer_info');
if (dbg) {
info.unmaskedVendor = gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL);
info.unmaskedRenderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);
} else {
info.unmaskedVendor = null;
info.unmaskedRenderer = null;
}
// Extensions list (useful fingerprinting signal)
info.extensions = gl.getSupportedExtensions() || [];
// Anisotropy (if available)
const extAniso = gl.getExtension('EXT_texture_filter_anisotropic') ||
gl.getExtension('MOZ_EXT_texture_filter_anisotropic') ||
gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
if (extAniso) {
const maxAniso = gl.getParameter(extAniso.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
info.maxAnisotropy = maxAniso;
}
return info;
}
يجمع تفاصيل متنوعة مثل المورد والطراز وحجم الذاكرة وأقصى حجم للنسيج وحدود المتغيرات الموحدة للمظللات وامتداد WebGL المدعوم وبيانات أخرى عن وحدة GPU لديك.
الخطوة 3. فحص دقات المظللات
function probeShaderPrecisions(gl) {
const precisions = {};
const shaderTypes = [
{type: gl.VERTEX_SHADER, name: 'vertex'},
{type: gl.FRAGMENT_SHADER, name: 'fragment'}
];
const levels = [{p: gl.LOW_FLOAT, name: 'low'}, {p: gl.MEDIUM_FLOAT, name: 'medium'}, {p: gl.HIGH_FLOAT, name: 'high'}];
for (const sh of shaderTypes) {
precisions[sh.name] = {};
for (const lv of levels) {
const pf = gl.getShaderPrecisionFormat(sh.type, lv.p);
precisions[sh.name][lv.name] = {rangeMin: pf.rangeMin, rangeMax: pf.rangeMax, precision: pf.precision};
}
}
return precisions;
}
تسأل مواقع الويب بعد ذلك وحدة GPU عن مستويات الدقة المدعومة لعمليات الفاصلة العائمة ومظللات الأجزاء، وتسجل نطاقاتها الرقمية.
بعد أن ينتهي البرنامج النصي من جمع معلومات العتاد والبرمجيات، ينتقل إلى الخطوة التالية: عرض الصورة.
الخطوة 4. اختبار عرض المشهد
// Simple shader pair: position-only vertex shader and color-fragment that depends on coords
const VERT = `#version 300 es
in vec2 a_pos;
out vec2 v_uv;
void main() {
v_uv = a_pos * 0.5 + 0.5;
gl_Position = vec4(a_pos, 0.0, 1.0);
}`;
const FRAG = `#version 300 es
precision highp float;
in vec2 v_uv;
out vec4 outColor;
void main() {
// deterministic function of coordinates that will exercise GPU math
float r = sin(v_uv.x * 123.456) * 0.5 + 0.5;
float g = cos(v_uv.y * 78.9) * 0.5 + 0.5;
float b = fract((v_uv.x + v_uv.y) * 9876.54321);
outColor = vec4(r, g, b, 1.0);
}`;
function createProgramWebGL2(gl, vertSrc, fragSrc) {
function compile(src, type) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
const err = gl.getShaderInfoLog(s);
gl.deleteShader(s);
throw new Error('Shader compile error: ' + err);
}
return s;
}
const vs = compile(vertSrc, gl.VERTEX_SHADER);
const fs = compile(fragSrc, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const err = gl.getProgramInfoLog(program);
throw new Error('Program link error: ' + err);
}
gl.deleteShader(vs);
gl.deleteShader(fs);
return program;
}
function renderTestAndHash(gl, canvas) {
const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext;
if (!isWebGL2) {
// For WebGL1 we'd need slightly different shaders (no #version, use attribute/varying)
// but the concept is the same. For clarity this example uses WebGL2 if available.
console.warn('Example uses WebGL2 for deterministic shader pipeline; falling back may require shader edits.');
}
// Create program & buffer
const prog = createProgramWebGL2(gl, VERT, FRAG);
gl.useProgram(prog);
const posLoc = gl.getAttribLocation(prog, 'a_pos');
const quad = new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1
]);
const vb = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vb);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
// Render to the canvas
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0,0,0,1);
gl.clear(gl.COLOR_BUFFER_BIT);
const t0 = performance.now();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.finish(); // ensure rendering completes
const t1 = performance.now();
const renderTime = t1 - t0; // render timing in ms
// Read pixels
const px = new Uint8Array(canvas.width * canvas.height * 4);
gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, px);
const pixelHash = fnv1aHashFromBytes(px);
// Clean up (optional)
gl.deleteBuffer(vb);
gl.deleteProgram(prog);
return {pixelHash, renderTime};
}
يطلب البرنامج النصي من متصفحك عرض مشهد ثنائي أو ثلاثي الأبعاد 2D أو 3D، قد يضم درجات لونية أو تدرجات أو تأثيرات محددة. ثم يقرأ بايتات البكسل ويستخلص ألوان البكسلات ونسيج الصورة والتظليل والتدرجات، ويحسب تجزئة لهذه القيم ويقيس زمن العرض.
الخطوة 5. دمج البيانات في بصمة
function collectWebGLFingerprint(opts = {}) {
const {gl, canvas} = createWebGLContext({webgl2Preferred: true, preserveDrawingBuffer: true});
if (!gl) throw new Error('No WebGL available');
const hw = getWebGLHardwareInfo(gl);
const precisions = probeShaderPrecisions(gl);
// optional: probe more capabilities
const supportsFloatTextures = !!gl.getExtension('OES_texture_float') || !!gl.getExtension('EXT_color_buffer_float');
// Render test and get pixel hash + timing
const {pixelHash, renderTime} = renderTestAndHash(gl, canvas);
// Combine into a fingerprint string
const parts = [
navigator.userAgent || '',
hw.unmaskedVendor || '',
hw.unmaskedRenderer || '',
hw.maxTextureSize,
hw.maxCombinedTextureImageUnits,
hw.maxVertexAttribs,
supportsFloatTextures ? 'floatTex' : '',
hw.extensions ? hw.extensions.join(',') : '',
JSON.stringify(precisions),
pixelHash,
String(Math.round(renderTime)) // include rounded render time
];
const raw = parts.join('|');
const fingerprint = fnv1aHashFromString(raw);
// Return a readable object
return {
fingerprint,
raw,
components: {
ua: navigator.userAgent,
hardware: hw,
precisions,
pixelHash,
renderTime
}
};
}
// Example usage:
try {
const fp = collectWebGLFingerprint();
console.log('WebGL fingerprint:', fp.fingerprint);
console.log('Components:', fp.components);
} catch (e) {
console.error('Could not collect WebGL fingerprint:', e);
}
أخيراً، يستدعي البرنامج النصي كل الخطوات السابقة ويدمج جميع البيانات التي حصل عليها في سلسلة طويلة واحدة، ثم يحسب تجزئتها مجدداً لإنشاء معرّف بصمة أكثر اختصاراً وقابلية للاستخدام.
تجري هذه العملية كاملة خلال أجزاء من الثانية وتبقى غير مرئية لك. وبعد جمع بصماتك، تخزنها مواقع الويب وتقارنها بقواعد البيانات الداخلية أو ترسلها إلى خدمات مكافحة الروبوتات لتحليلها. وإذا طابقت بصمة روبوتاً معروفاً أو مستخدماً مشبوهاً، فقد تتعرض للحظر.
تحديات انتحال WebGL
توجد عدة أسباب تجعل التوافق مع إرشادات حركة المرور أمراً صعباً.
- قد لا ترى العين البشرية اختلافات سلوك العرض في GPU، لكنها واضحة لمواقع الويب وتبقى متسقة عبر الصفحات. ولا يفيد حذف ملفات تعريف الارتباط أو استخدام وضع التصفح المتخفي.
- يعتمد العرض على بيانات تأتي من عتاد فعلي، وهو بعيد عن متناول تعديلات JavaScript وإضافات المتصفح.
- يمكنك تغيير معلمات WebGL كلما حملت صفحة، لكن إذا ادعيت أن وحدة GPU لديك من Intel بينما تعرض الرسومات كما لو كانت من NVIDIA، فستكتشف مواقع الويب عدم الاتساق، وهذا يكفي لتفعيل أنظمة مكافحة الروبوتات.
- تلتقط غالبية أنظمة الكشف الحديثة محاولات العبث بـ API الخاصة بـ WebGL.
هل يعني ذلك أنه لا توجد طريقة لتجاوز بصمة WebGL؟ لا، فهناك عدة تقنيات فعالة بالفعل. لكن لنبدأ من البداية.
هل انتحال بصمة WebGL قانوني؟
من حقك حماية خصوصيتك، لذا من الخطأ القول إن انتحال بصمة WebGL غير قانوني. كما أنه يستخدم لأغراض الاختبار. لكن الاعتماد على الانتحال لارتكاب الاحتيال أو انتهاك شروط خدمة مواقع الويب مخالف للقانون. ينبغي دائماً قراءة شروط الخدمة وتجنب أي أفعال غير قانونية قد تضر بخصوصية الأطراف الأخرى. تُقدَّم المعلومات التالية لأغراض تعليمية فقط. لا تشجع DataImpulse على اتخاذ أي إجراءات ولا تتحمل مسؤولية النتائج المحتملة.
طرق مثبتة لانتحال بصمات WebGL
- استخدم المتصفحات عديمة الواجهة مع مكتبات إضافية
أدوات شائعة مثل Playwright أو Selenium، والمدعومة بأدوات مثل Playwright Stealth أو Undetected Chrome Driver، يمكنها تعديل معلمات مثل حجم الشاشة وغيرها تعديلاً طفيفاً. لكن عليك توخي الحذر لضمان تطابق المتغيرات المزيفة والصور المعروضة. لا تنجح هذه الطريقة في المشاريع واسعة النطاق أو عند محاولة تجريف مواقع الويب التي تستخدم أحدث خوارزميات كشف الروبوتات. وينطبق الأمر نفسه على إضافات المتصفح مثل WebGL Fingerprint Defender أو CanvasBlocker. فهي تعترض استدعاءات API الخاصة بـ WebGL وتعيد بيانات معدلة، لكن الصورة المعروضة قد لا تزال غير متطابقة معها.
- اعتمد افتراضية GPU
يمكنك جعل وحدة GPU في المتصفح أو برامج تشغيلها أو كليهما تبدو مختلفة عن وحدة GPU فعلية. وهناك عدة أساليب يمكنك اتباعها، مثل استبدال وحدة GPU فعلية بعارض برمجي مثل Switch Shader أو استخدام آلات افتراضية مع برامج تشغيل GPU متنوعة. من ناحية أخرى، إن كانت ميزانيتك محدودة أو كنت تفتقر إلى مهارات تقنية قوية، ففكر مرتين قبل اعتماد هذه الأساليب. كما أن توسيع نطاق مشاريع التجريف لديك قد يكون مهمة صعبة.
- اطلب واجهات API ثابتة
تنجح هذه الطريقة إذا كنت تجرّف بيانات عامة وكان موقع الويب المستهدف يوفر API عامة أو مخفية تُرجع JSON. أما في مواقع الويب الديناميكية المحمية فليست خياراً متاحاً، لكنها في حالات أخرى تستحق الاهتمام لأن نقاط نهاية API تكون عادة أقل حماية.
- تعطيل WebGL
يقطع تعطيل WebGL جميع إشارات بصمة WebGL الأساسية، مثل المورد والعارض والامتدادات والدقة وتجزئات البكسل وأزمنة العرض.
كيفية تعطيل WebGL
Chrome
الطريقة 1: عبر علامات Chrome:
في شريط العنوان، اكتب chrome://flags، اضغط إدخال، ثم اختر “تعطيل WebGL” أو –disable-webgl. اضبط العلامة على “معطّل”. ولتفعيل التغييرات، أعد تشغيل Chrome.
الطريقة 2: عبر سطر الأوامر:
انقر بزر الماوس الأيمن على أيقونة Google Chrome وانتقل إلى “الخصائص”. ابحث عن السطر “الهدف” وينبغي أن ينتهي بشيء مثل chrome.exe. في نهاية السطر تماماً، أضف مسافة واكتب –disable-webgl. انقر على “تطبيق” ثم “موافق”.
Firefox
في شريط العنوان، اكتب about:config واضغط إدخال. إذا ظهر تحذير، فوافق عليه. ابحث عن webgl.disable. انقر نقراً مزدوجاً واضبط قيمته على true.
Safari
انتقل إلى تفضيلات Safari، وافتح علامة التبويب “متقدم”، واختر “تطوير“، إن كانت متاحة. انتقل إلى الميزات التجريبية وابحث عن خيار WebGL عبر Metal. أزل تحديد المربع وأعد تشغيل المتصفح.
لكن توخ الحذر، إذ قد لا تعمل مواقع ويب كثيرة على نحو صحيح عند تعطيل WebGL.
استغلال ثغرات بصمة WebGL
يمكنك استخدام بعض الحيل البسيطة. فعلى سبيل المثال، غالباً ما تخزن البرامج النصية النتائج مؤقتاً وتعيد استخدامها بدلاً من إعادة حسابها من الصفر. يمكنك اكتشاف ذلك وتغيير بيئتك قبل إعادة استخدام الذاكرة المؤقتة، لمنع ربطك بهوية مسجلة سابقاً، أو إجبار الموقع على إعادة حساب قيمة.
الكاشف:
// Helper: run the fingerprinting routine and return {time, result}
async function runFingerprintTrial(triggerFn) {
const t0 = performance.now();
const result = await triggerFn(); // should return fingerprint/hash if available
const time = performance.now() - t0;
return { time, result };
}
async function detectCachingRobust(triggerFingerprinting, trials = 5) {
const runs = [];
for (let i = 0; i < trials; i++) {
// small delay to reduce micro-scheduling bias
if (i > 0) await new Promise(r => setTimeout(r, 30));
runs.push(await runFingerprintTrial(triggerFingerprinting));
}
// compute median time
const times = runs.map(r => r.time).sort((a,b) => a-b);
const medianTime = times[Math.floor(times.length/2)];
// compare first vs median of later runs
const firstTime = runs[0].time;
const laterMedian = times.slice(1).length ? times.slice(1)[Math.floor((times.length-1)/2)] : medianTime;
// check result stability (if trigger returns a fingerprint/hash)
const results = runs.map(r => r.result);
const allSame = results.every(x => x === results[0]);
// simple heuristic: if first run is much slower *and* results identical, likely cached
const speedRatio = firstTime / (laterMedian || 1);
const likelyCached = (speedRatio > 5) && allSame;
return {
runs,
medianTime,
firstTime,
laterMedian,
speedRatio,
results,
allSame,
likelyCached
};
}
فرض بيئة جديدة:
function newWebGLContext(w=256,h=256) {
const c = document.createElement('canvas');
c.width = w; c.height = h;
const gl = c.getContext('webgl2') || c.getContext('webgl') || c.getContext('experimental-webgl');
return {c, gl};
}
فرض إعادة الحساب:
localStorage.removeItem('fp_token'); // only if you can identify it
// or flush likely storage (be careful: may break site)
localStorage.clear();
indexedDB.deleteDatabase('site-db-name'); // needs precise names
نهج آخر هو اكتشاف وقت إضافة الصفحة لبرنامج نصي للبصمة وتشغيل برنامج نصي للانتحال مسبقاً. لكن انتبه إلى أنه سهل الاكتشاف وقد يعطل سلوك الصفحة، لذا فهذه الطريقة أنسب لأغراض الاختبار أو الدفاع.
// Robust script interception (best run at document_start)
(function() {
if (window.__scriptInterceptorInstalled) return;
window.__scriptInterceptorInstalled = true;
// Your spoofing routine — define this to patch WebGL or other APIs.
function injectSpoofing() {
try {
// Example: block debug info (replace with your real routine)
const origGetExt = WebGLRenderingContext.prototype.getExtension;
WebGLRenderingContext.prototype.getExtension = function(name) {
if (name === 'WEBGL_debug_renderer_info') return null;
return origGetExt.call(this, name);
};
// mark that spoofing ran
window.__spoofingInjected = true;
} catch (e) {
// swallow errors — don't break page
console.error('injectSpoofing error', e);
}
}
// Helper: decide whether a script looks like fingerprinting (tweak to match)
function looksLikeFingerprintScript(srcOrContent) {
if (!srcOrContent) return false;
const s = String(srcOrContent).toLowerCase();
return s.includes('fingerprint') || s.includes('fingerprinting') || s.includes('fingerprintjs') || s.includes('fingerprint2');
}
// 1) Hook HTMLScriptElement.prototype.src property (setter)
try {
const proto = HTMLScriptElement.prototype;
const srcDesc = Object.getOwnPropertyDescriptor(proto, 'src');
if (srcDesc && srcDesc.configurable) {
Object.defineProperty(proto, 'src', {
configurable: true,
enumerable: srcDesc.enumerable,
get: function() { return srcDesc.get.call(this); },
set: function(val) {
try {
// If the script's src looks suspicious, inject before setting
if (looksLikeFingerprintScript(val) && !window.__spoofingInjected) {
injectSpoofing();
}
} catch (e) { /* ignore */ }
return srcDesc.set.call(this, val);
}
});
}
} catch (e) {
// non-fatal
}
// 2) Hook setAttribute on script elements (covers element.setAttribute('src', ...))
try {
const origSetAttr = HTMLScriptElement.prototype.setAttribute;
HTMLScriptElement.prototype.setAttribute = function(name, value) {
try {
if ((name + '').toLowerCase() === 'src' && looksLikeFingerprintScript(value) && !window.__spoofingInjected) {
injectSpoofing();
}
} catch (e) {}
return origSetAttr.call(this, name, value);
};
} catch (e) {}
// 3) As a fallback, monitor append/insert to catch scripts that already had src set
try {
const origAppend = Node.prototype.appendChild;
Node.prototype.appendChild = function(node) {
try {
if (node && node.tagName && node.tagName.toLowerCase() === 'script') {
const src = node.src || node.getAttribute && node.getAttribute('src');
if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
injectSpoofing();
}
}
} catch (e) {}
return origAppend.call(this, node);
};
const origInsertBefore = Node.prototype.insertBefore;
Node.prototype.insertBefore = function(node, ref) {
try {
if (node && node.tagName && node.tagName.toLowerCase() === 'script') {
const src = node.src || node.getAttribute && node.getAttribute('src');
if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
injectSpoofing();
}
}
} catch (e) {}
return origInsertBefore.call(this, node, ref);
};
} catch (e) {}
// 4) Small DOM observer as an extra safety net (lightweight)
try {
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const n of m.addedNodes || []) {
try {
if (n && n.tagName && n.tagName.toLowerCase() === 'script') {
const src = n.src || (n.getAttribute && n.getAttribute('src')) || n.textContent;
if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
injectSpoofing();
obs.disconnect();
return;
}
}
} catch (e) {}
}
}
});
obs.observe(document, { childList: true, subtree: true });
} catch (e) {}
// If the page already has script tags at start, scan them now
try {
const scripts = document.getElementsByTagName('script');
for (let i = 0; i < scripts.length; i++) {
const s = scripts[i];
const src = s.src || s.getAttribute && s.getAttribute('src') || s.textContent;
if (looksLikeFingerprintScript(src) && !window.__spoofingInjected) {
injectSpoofing();
break;
}
}
} catch (e) {}
})();
كذلك، لا تستطيع بيئات WebGL في الصفحات المختلفة رؤية بعضها بعضاً. يمكنك إنشاء سياقات معزولة لمنع مواقع الويب من مطابقة بصماتك عبر الصفحات.
WebGL مقابل Canvas
غالباً ما تستخدم بصمة WebGL وبصمة Canvas بالتبادل، وصحيح أنهما مرتبطتان ارتباطاً وثيقاً. لكنهما ليسا الشيء نفسه بنسبة 100%.
تحلل بصمة Canvas كيفية عرض المتصفحات للرسومات ثنائية الأبعاد 2D باستخدام HTML5 Canvas. وتعتمد نتائج العرض أساساً على حزمة الرسومات في المتصفح ومحرك العرض. لذلك يسهل انتحالها.
تحلل بصمة WebGL كيفية عرض المتصفح للرسومات ثنائية وثلاثية الأبعاد 2D و3D معاً. وتعتمد نتائج العرض على خصائص العتاد، ما يجعل هذه الطريقة أغنى بالمعلومات وأصعب في التجاوز.
الخلاصة
بعد أن عرفت تحديات انتحال بصمة WebGL، قد تظن أنك كنت أفضل حالاً قبل عشر دقائق حين لم تكن تعرف شيئاً عنها. لكن هدفك ليس أن تصبح غير مرئي، بل أن تعطي انطباع “مستخدم عادي”. ولتحقيق ذلك، ينبغي أن تبدو بصمتك واقعية وأن تتسق ضمن الجلسة وتتغير من جلسة إلى أخرى. وهذا ممكن تماماً. ومن جهة أخرى، لا ينجح تجريف الويب إلا عندما تحافظ على السرية على جميع المستويات وتستخدم متصفحات متعددة تسجيل الدخول، حلول انتحال TLS، بروكسيات، وغير ذلك. تعزز كل هذه الأدوات والأساليب بعضها بعضاً، ما يتيح لك البقاء بعيداً عن الأنظار. لدى DataImpulse ما يلائم خصوصيتك وإخفاء هويتك: بروكسيات مستمدة قانونياً ومدرجة في القائمة المسموح بها لتلبية احتياجات التجريف الشاملة، إلى جانب خدمة دعم بشري على مدار 24/7 لتتمكن من جمع البيانات بلا انقطاع. انقر على “جرّب الآن” أو تواصل معنا عبر [email protected] للبدء.

