BreakHub
راهنماهااثر انگشت مرورگر

سایت‌ها چطور تصمیم می‌گیرند که شما ربات هستید

مرورگرهای headless و اسکریپت‌های خودکارسازی در navigator، شیء window، پروتکل DevTools و لایهٔ شبکه رد می‌گذارند. این مقاله توضیح می‌دهد هر سیگنال از کجا می‌آید، چرا هیچ‌کدام به‌تنهایی قابل اتکا نیست، و چرا کاربران عادی هم گاهی ربات تشخیص داده می‌شوند.

7 دقیقه مطالعه · انتشار ۱۹ شهریور ۱۴۰۵

سایتی را باز می‌کنید، هنوز روی هیچ‌چیز کلیک نکرده‌اید، و آن کادر چرخان Cloudflare همین حالا تصمیم گرفته که شما را عبور دهد. بر چه مبنایی؟ انبوهی از سیگنال‌های کوچک که هر کدام به‌تنهایی نامطمئن است، اما وقتی با وزن‌های مختلف کنار هم قرار می‌گیرند، قضاوتی به دست می‌دهند که به اندازهٔ کافی خوب است. کارت تشخیص ربات این سایت چند تا از پایدارترین سیگنال‌های همین مجموعه را به کار می‌گیرد. در ادامه می‌بینیم هر کدام از کجا می‌آید.

سه حکم

سیستم‌های ریسک معمولاً بازدیدکنندگان را در سه دسته می‌گذارند. خزنده‌های موتورهای جستجو (Googlebot، Bingbot، Baiduspider) و پایشگرهای uptime «ربات‌های خوب» هستند: آشکارا خودشان را معرفی می‌کنند، reverse DNS هویتشان را تأیید می‌کند و بیشتر سایت‌ها از آن‌ها استقبال می‌کنند. اسکریپت‌هایی که با Selenium، Puppeteer یا Playwright هدایت می‌شوند، به‌علاوهٔ خزنده‌هایی که خودشان را جای موتور جستجو جا می‌زنند، «ربات‌های بد» هستند؛ همان‌هایی که credential stuffing، ثبت‌نام جعلی، سوءاستفاده از کد تخفیف و scraping انجام می‌دهند. بقیه «احتمالاً انسان» هستند.

به کلمهٔ «احتمالاً» دقت کنید. هیچ سیگنالی ثابت نمی‌کند که آدمی پشت صندلی نشسته است. بیشترین چیزی که یک سیستم می‌تواند بگوید این است که این محیط شبیه محیطی تحت کنترل اسکریپت به نظر نمی‌رسد.

صریح‌ترین سیگنال. مشخصات WebDriver در W3C الزام می‌کند که هر وقت مرورگر تحت خودکارسازی باشد، navigator.webdriver برابر true باشد. مرورگرهایی که Selenium، Playwright و Puppeteer راه می‌اندازند به‌طور پیش‌فرض این شرط را رعایت می‌کنند، پس بررسی آن یک خط کد است.

طبیعتاً اولین کاری که هر نویسندهٔ scraper می‌کند حذف همین است. افزونه‌های stealth این ویژگی را با Object.defineProperty بازنویسی می‌کنند یا با flagهای راه‌اندازی غیرفعالش می‌کنند. اما یک حذف سرسری، ردهای تازه‌ای به جا می‌گذارد. Object.getOwnPropertyDescriptor(navigator, 'webdriver') باید undefined برگرداند، چون این ویژگی روی prototype قرار دارد؛ پس از بازنویسی، یک descriptor برمی‌گرداند. getter بازنویسی‌شده دیگر به شکل function get webdriver() { [native code] } رشته‌ای نمی‌شود. بررسی از «آیا flag تنظیم شده» به «آیا کسی به flag دست زده» تغییر می‌کند.

چیزهایی که در Chrome غیبشان زده

یک Chrome دسکتاپ عادی تقریباً همیشه چند چیز دارد: یک شیء window.chrome (با runtime، loadTimes و مانند آن)، سه تا پنج ورودی داخلی در navigator.plugins (PDF Viewer، Chromium PDF Plugin و غیره) و یک navigator.languages غیرخالی.

نسخه‌های اولیهٔ Chrome headless شیء window.chrome نداشت، فهرست افزونه‌هایش خالی بود و گاهی فهرست زبان‌هایش هم خالی بود. UA آن به معنای واقعی کلمه می‌گفت HeadlessChrome؛ PhantomJS پیش از آن نام خودش را اعلام می‌کرد. این‌ها اولین ترفندهای تشخیص بودند و اولین‌هایی که وصله شدند. حالت headless جدیدتر (--headless=new) حالا تقریباً از Chrome معمولی قابل تشخیص نیست.

با این حال شمارش افزونه‌ها احتیاط می‌خواهد. Chrome موبایل عمداً هیچ افزونه‌ای ندارد و Firefox مدت‌هاست فهرست افزونه‌ها را نشان نمی‌دهد. پس «صفر افزونه» فقط به شرط «ادعای Chrome دسکتاپ» سیگنال است؛ در غیر این صورت مثبت کاذب خالص است. به همین دلیل در امتیاز اصالت ما فقط پنج امتیاز کم می‌کند.

پنجره‌ها و مجوزهایی که با خودشان در تناقض‌اند

محیط headless پنجرهٔ واقعی ندارد، بنابراین window.outerWidth و outerHeight اغلب ۰ هستند، یا رابطهٔ آن‌ها با innerWidth/innerHeight بی‌معناست؛ مثلاً ناحیهٔ محتوا بزرگ‌تر از قاب است، یا screen.availWidth از screen.width بیشتر است. مرورگری که یک انسان به آن نگاه می‌کند چنین چیزی ندارد.

تناقض کلاسیک دیگر به مجوز اعلان‌ها مربوط است: Notification.permission مقدار denied برمی‌گرداند در حالی که navigator.permissions.query({name: 'notifications'}) می‌گوید prompt. در یک مرورگر عادی هر دو API هم‌نظرند؛ فقط در برخی پیکربندی‌های headless با هم اختلاف دارند.

ردپاهای CDP

Puppeteer و Playwright مرورگر را از طریق Chrome DevTools Protocol هدایت می‌کنند، و همین که پروتکل متصل شود، عوارض جانبی دارد. معروف‌ترینش: وقتی DevTools یک شیء Error را سریال می‌کند، ویژگی stack آن را می‌خواند. اسکریپت تشخیص می‌تواند یک Error بسازد، روی stack یک getter بگذارد و شیء را به console.debug() بدهد. بدون DevTools این getter هرگز اجرا نمی‌شود؛ با CDP متصل، اجرا می‌شود. به همین ترتیب، Runtime.enable تغییرات قابل تشخیصی در context اجرای صفحه به جا می‌گذارد.

این ترفندها هرچقدر هم زیرکانه باشند، بزرگ‌ترین منبع مثبت کاذب هم هستند: کافی است F12 را بزنید و خودتان DevTools را باز کنید تا دقیقاً همان‌ها فعال شوند. به همین دلیل ما فقط کم‌خطاترین‌شان را به کار می‌بریم و نتیجه را «فقط اطلاعی» برچسب می‌زنیم. طرف مقابل هم عقب نمی‌ماند: نسخه‌های جدیدتر Puppeteer و وصله‌های گوناگون از Runtime.enable پرهیز می‌کنند، و این رقابت ادامه دارد.

رفتار و شبکه: سیگنال‌هایی فراتر از اسکریپت

هر چیزی که تا اینجا گفتیم، ویژگی ایستای محیط JS است. کار اصلی در سیستم‌های ریسک واقعی در دو جای دیگر انجام می‌شود.

رفتار. مسیر ماوس انسان منحنی و لرزان است، روی چیزها مکث می‌کند، تردید دارد. مسیرهای اسکریپتی از A به B در یک خط راست می‌روند و فاصلهٔ کلیک‌ها مثل مترونوم منظم است. ریتم تایپ، شتاب اسکرول و زمان ماندن روی صفحه همگی با آنچه آدم‌ها واقعاً انجام می‌دهند مقایسه می‌شود.

شبکه. «کاربر عادی»‌ای که از IP یک مرکز داده (AWS، Google Cloud، هر ارائه‌دهندهٔ VPS) می‌آید ذاتاً عجیب است؛ مقالهٔ کیفیت IP را ببینید. سخت‌گیرانه‌تر از آن، اثر انگشت TLS است. کتابخانهٔ requests در Python هنگام مذاکرهٔ TLS ترتیب مجموعه‌های رمز و فهرست افزونه‌هایی دارد (اثر انگشت JA3/JA4 آن) که هیچ شباهتی به Chrome ندارد. آن را کنار یک UA از Chrome بگذارید و لباس مبدل در لایهٔ TLS، پیش از اجرای هر JavaScript، از هم می‌پاشد.

کنار هم گذاشتن همه‌چیز

هر سیگنالی که در بالا آمد، مثال نقض دارد. webdriver را می‌توان حذف کرد، شمارش افزونه‌ها را جعل کرد، مسیر ماوس را لرزاند، IP مرکز داده را با پروکسی خانگی عوض کرد. به همین دلیل Turnstile، reCAPTCHA v3 و SDKهای تجاری ریسک قواعد را یکی‌یکی ارزیابی نمی‌کنند. به هر سیگنال وزن می‌دهند، بررسی می‌کنند که آیا سیگنال‌ها یکدیگر را تأیید می‌کنند، همه را به مدل‌هایی می‌دهند که روی حجم بزرگی از ترافیک آموزش دیده‌اند، و یک امتیاز بیرون می‌دهند. امتیازهای بالا بی‌سروصدا عبور می‌کنند، امتیازهای متوسط با یک چالش روبه‌رو می‌شوند (یک spinner، یک شبکهٔ تصویر)، و امتیازهای پایین مسدود می‌شوند.

به همین دلیل هم هست که «دور زدن تشخیص» یک مسابقهٔ تسلیحاتی دائمی است: یک سیگنال را درست کنید، وزن بقیه بالا می‌رود.

مثبت کاذب: انسان‌هایی که ربات تشخیص داده می‌شوند

اگر از طرف دیگر نگاه کنیم، کدام آدم‌های واقعی به اشتباه علامت می‌خورند؟

  • کسانی که افزونهٔ حریم خصوصی دارند. این افزونه‌ها ویژگی‌های navigator را تغییر می‌دهند، canvas را مسدود می‌کنند، فهرست افزونه‌ها را خالی می‌کنند، و خود این تغییرات شبیه لباس مبدل به نظر می‌رسد.
  • ماشین‌های سازمانی. Group policy افزونه‌ها را غیرفعال می‌کند، زبان را قفل می‌کند، محیط مجازی را یکسان‌سازی می‌کند، و یک ناوگان کامل یک اثر انگشت مشترک دارد.
  • مرورگرهای قدیمی یا کم‌کاربرد، و دسکتاپ‌های Linux. نمونه‌ها کم است، پس مدل‌ها کمتر مطمئن‌اند.
  • توسعه‌دهندگانی که با DevTools باز اشکال‌زدایی می‌کنند. هر بررسی از نوع CDP فعال می‌شود.

به همین دلیل کارت ما پاسخ را به بله/خیر تقلیل نمی‌دهد. فهرست می‌کند کدام سیگنال‌ها فعال شده‌اند. دیدن این فهرست به شما می‌گوید کدام افزونه یا تنظیم مسئول است، به‌جای اینکه به یک ضربدر قرمز خیره بمانید.

پرسش‌های متداول

من scraper نمی‌نویسم. چرا باید برایم مهم باشد؟ چون مثبت کاذب گریبان شما را می‌گیرد. یک افزونهٔ حریم خصوصی، یک خروجی VPN و یک دسکتاپ Linux روی هم کافی است تا تعداد قابل توجهی از سایت‌ها مدام برایتان CAPTCHA بیاورند. دانستن اینکه کدام سیگنال فعال شده، همان چیزی است که اجازه می‌دهد یک چیز مشخص را تغییر دهید.

آیا وصله کردن navigator.webdriver کافی است؟ نه، و یک وصلهٔ سرسری سیگنال «دستکاری‌شده» را هم روی آن اضافه می‌کند. تشخیص مدرن سازگاری کلی را امتیاز می‌دهد، نه یک flag خاص را.

آیا بررسی ربات این سایت داده‌های من را جایی می‌فرستد؟ نه. همهٔ بررسی‌ها به‌صورت محلی در مرورگر شما اجرا می‌شود. فقط اگر در آمار مشارکت کنید، هش ویژگی‌ها ارسال می‌شود، هرگز مقادیر خام.