Rò rỉ CDP trong Puppeteer: cách các hệ thống chống gian lận phát hiện tự động hóa thông qua Chrome DevTools Protocol

Rò rỉ CDP trong Puppeteer: cách các hệ thống chống gian lận phát hiện tự động hóa thông qua Chrome DevTools Protocol
Markus_automation
Markus_automation

Expert in data parsing and automation

Trong số các công cụ tự động hóa trình duyệt, Puppeteer từ lâu đã giữ một vị trí đặc biệt. Không giống như hệ sinh thái Selenium nặng nề, nó cung cấp cho các nhà phát triển quyền kiểm soát gốc, hiệu suất cao đối với Chromium ngay khi xuất xưởng trong môi trường Node.js.

Puppeteer đã trở thành một tiêu chuẩn của ngành nhờ vào hệ sinh thái khổng lồ gồm các plugin có sẵn và sự tích hợp sâu sắc với công cụ V8. Bạn có thể sử dụng Puppeteer, kết nối puppeteer-extra-plugin-stealth, mua các proxy chất lượng cao, giả lập kỹ lưỡng vân tay Canvas và WebGL, và đối với nhiều tác vụ, điều đó là đủ.

Tuy nhiên, trên các trang web được bảo vệ tốt, một công cụ cào dữ liệu như vậy có thể gặp khó khăn. Các hệ thống bảo vệ như Cloudflare, Akamai hoặc DataDome có thể bắt đầu chặn các phiên làm việc của bạn. Tại sao điều này lại xảy ra?

Vấn đề không nằm ở logic mã của bạn hay chất lượng của các proxy. Nguyên nhân nằm ngay ở nền tảng tạo nên sự mạnh mẽ và tiện lợi của Puppeteer: Giao thức Chrome DevTools (CDP). Giao thức điều khiển cấp thấp này để lại các dấu vết kỹ thuật số đặc trưng mà các thuật toán chống gian lận hiện đại có thể phát hiện và sử dụng để nhận dạng bot.

Hãy cùng kiểm tra xem các rò rỉ này xảy ra như thế nào và tại sao các kỹ thuật giả lập hời hợt cho các thiết lập Puppeteer tiêu chuẩn không còn hiệu quả nữa.

Nội dung

Duy trì danh tính ẩn trực tuyến của bạn với Octo Browser. Dấu vân tay số thật của bạn sẽ không thể bị truy vết.

CDP là gì, và tại sao nó lại để lại dấu vết kỹ thuật số?

Giao thức Chrome DevTools (CDP) cung cấp quyền truy cập cấp thấp vào kiến trúc của trình duyệt. Ban đầu nó được thiết kế để gỡ lỗi, kiểm tra và phân tích hiệu năng, chứ không phải để thu thập dữ liệu ẩn danh.

Giao thức này hoạt động thông qua các kết nối WebSocket tương tác trực tiếp với công cụ V8. Khi tập lệnh của bạn gửi một lệnh, chẳng hạn như nhấp chuột hoặc điều hướng trang, trình duyệt sẽ mở một socket cục bộ để giao tiếp hai chiều. Quá trình này chắc chắn sẽ tạo ra các nhật ký nội bộ, ảnh hưởng đến việc phân bổ bộ nhớ và để lại các tạo tác bên trong môi trường trình duyệt bị cô lập. Các hệ thống bảo mật có thể phân tích các thay đổi vi mô và mô hình thời gian này để phát hiện hành vi tự động. Do đó, chỉ riêng sự tồn tại của kết nối cũng có thể tiết lộ sự tự động hóa trình duyệt.

Sự khác biệt giữa rò lỉ CDP và dấu vân tay truyền thống

Điều quan trọng là phải hiểu rằng rò rỉ CDP và rò lỉ vân tay trình duyệt đại diện cho hai vectơ phát hiện hoàn toàn khác nhau.

  • Dấu vân tay truyền thống (Canvas, WebGL, Fonts) xác định các đặc tính phần cứng độc nhất và hành vi kết xuất. Sự không nhất quán của vân tay hoặc lỗi giả mạo cho phép các hệ thống chống bot phát hiện thao túng và phân loại người dùng là đáng ngờ.

  • Rò rỉ CDP tiết lộ các chỉ báo cấu trúc và hành vi của quá trình thực thi mã. Chúng phơi bày thực tế rằng trình duyệt đang bị điều khiển từ xa.

Bạn có thể có một vân tay WebGL độc nhất hoàn hảo, nhưng các cờ tự động hóa có thể ngay lập tức làm mất hiệu lực lợi thế đó. Việc kiểm tra vân tay trình duyệt đòi hỏi tài nguyên và thời gian xử lý đáng kể, trong khi việc kiểm tra các biến môi trường hoặc ngăn xếp cuộc gọi hầu như là miễn phí. Đây chính là lý do tại sao các hệ thống chống gian lận ưa chuộng phát hiện dựa trên CDP. Nó cho phép họ lọc bỏ các bot trong quá trình kiểm tra cấp giao thức cơ bản, giúp tiết kiệm tài nguyên máy chủ vốn phải chi cho phân tích hành vi chuyên sâu tốn kém hơn.

Giải phẫu rò rỉ Giao thức Chrome DevTools

Các dấu vết của Ngữ cảnh Thực thi (Execution Context)

Một trong những điểm yếu chính của Puppeteer nằm ở cách chính xác mà nó thực thi mã của bạn bên trong trình duyệt. Dưới mui xe, phương thức page.evaluate() dựa vào lệnh CDP Runtime.evaluate.

Khi một tập lệnh được chèn vào trình duyệt, các phiên bản Puppeteer hiện đại sẽ tạo ra một URL Nguồn cụ thể liên kết với phân đoạn mã đó. Nếu xảy ra lỗi trong quá trình thực thi tập lệnh, công cụ V8 sẽ tạo ra một dấu vết ngăn xếp (stack trace) tiêu chuẩn có thể chứa các mục như:

at pptr:evaluate;C:\Users\Admin\Projects\...\main.js:17:14
at pptr:evaluate;C:\Users\Admin\Projects\...\main.js:17:14

Bằng cách ghi đè các hàm trình duyệt cơ bản, các hệ thống chống gian lận có thể cố ý kích hoạt các lỗi vô hình và kiểm tra đối tượng Error.stack. Họ có thể phát hiện ra:

  • Tiền tố pptr:, một tham chiếu trực tiếp đến Puppeteer.

  • Một đường dẫn tuyệt đối đến một tệp trên máy cục bộ hoặc máy chủ của bạn, bắt đầu bằng C:\ hoặc /var/www/....

Trình duyệt của một người dùng thực sự không bao giờ thực thi mã từ các đường dẫn hệ thống tệp cục bộ khi truy cập một trang web công cộng. Do đó, các dấu vết như vậy đại diện cho bằng chứng xác thực của sự tự động hóa.

Execution Context markers

Thay đổi địa chỉ IP hoặc giả mạo vân tay Canvas sẽ không giải quyết được vấn đề này. Để ẩn các dấu vết này, bạn phải sửa đổi chính thư viện hoặc môi trường thực thi trình duyệt.

Giải pháp đáng tin cậy nhất là xóa trực tiếp dấu vết pptr:evaluate khỏi mã nguồn của Puppeteer trước khi các lệnh được gửi đến trình duyệt. Vì việc tìm kiếm và chỉnh sửa thủ công các tệp sau mỗi lần cài đặt npm là không thực tế, một trình vá lỗi đơn giản có thể được sử dụng:

const fs = require('fs');
const path = require('path');

// Path to ExecutionContext.js in recent Puppeteer versions
const targetFile = path.resolve(__dirname, 'node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js');

if (fs.existsSync(targetFile)) {
    let content = fs.readFileSync(targetFile, 'utf8');
    // Replace the pptr:evaluate prefix with an anonymous call
    // and remove the local file path
    const patchedContent = content.replace(/pptr:evaluate;.*?\\n/g, 'anonymous:evaluation;\n');
    fs.writeFileSync(targetFile, patchedContent, 'utf8');
    console.log('Puppeteer patched successfully. pptr:evaluate markers removed.');
}
const fs = require('fs');
const path = require('path');

// Path to ExecutionContext.js in recent Puppeteer versions
const targetFile = path.resolve(__dirname, 'node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js');

if (fs.existsSync(targetFile)) {
    let content = fs.readFileSync(targetFile, 'utf8');
    // Replace the pptr:evaluate prefix with an anonymous call
    // and remove the local file path
    const patchedContent = content.replace(/pptr:evaluate;.*?\\n/g, 'anonymous:evaluation;\n');
    fs.writeFileSync(targetFile, patchedContent, 'utf8');
    console.log('Puppeteer patched successfully. pptr:evaluate markers removed.');
}

Nếu việc sửa đổi node_modules không phải là một lựa chọn, các dấu vết có thể được lọc tại thời điểm chạy bằng cách chèn một tập lệnh giả mạo trước khi trang mục tiêu tải. Tập lệnh này ghi đè hành vi của đối tượng Error gốc và làm sạch các dấu vết ngăn xếp:

await page.evaluateOnNewDocument(() => {
    // Save the original Error constructor
    const NativeError = window.Error;

    window.Error = function(...args) {
        const err = new NativeError(...args);
        const originalStack = err.stack;

        if (originalStack) {
            Object.defineProperty(err, 'stack', {
                get: function() {
                    // Break stack trace into strings and remove Puppeteer-related entries from it
                    return originalStack
                        .split('\n')
                        .filter(line => !line.includes('pptr:evaluate'))
                        .join('\n');
                }
            });
        }
        return err;
    };
    
    // Restore the prototype chain to avoid detection
    window.Error.prototype = NativeError.prototype;
});
await page.evaluateOnNewDocument(() => {
    // Save the original Error constructor
    const NativeError = window.Error;

    window.Error = function(...args) {
        const err = new NativeError(...args);
        const originalStack = err.stack;

        if (originalStack) {
            Object.defineProperty(err, 'stack', {
                get: function() {
                    // Break stack trace into strings and remove Puppeteer-related entries from it
                    return originalStack
                        .split('\n')
                        .filter(line => !line.includes('pptr:evaluate'))
                        .join('\n');
                }
            });
        }
        return err;
    };
    
    // Restore the prototype chain to avoid detection
    window.Error.prototype = NativeError.prototype;
});
As a result, instead of exposing local file paths, the stack trace will display something like this

Kết quả là, thay vì để lộ các đường dẫn tệp cục bộ, dấu vết ngăn xếp sẽ hiển thị một cái gì đó như thế này

Lỗ hổng của Page.addScriptToEvaluateOnNewDocument

Các nỗ lực nhằm che giấu sự tự động hóa trình duyệt, bao gồm cả việc sử dụng các plugin ẩn danh phổ biến, thường dựa vào việc chèn JavaScript giả mạo trước khi trang web mục tiêu tải. Trong Puppeteer, việc này thường được thực hiện thông qua lệnh Page.addScriptToEvaluateOnNewDocument.

Tuy nhiên, bản thân việc sử dụng phương thức này lại để lại những dấu vết rõ rệt trong môi trường thực thi.

  1. Sự bất thường về thời gian. Lệnh này buộc công cụ V8 phải thực thi mã của bạn một cách đồng bộ khi ngữ cảnh trang được tạo, trước khi trình phân tích cú pháp HTML bắt đầu công việc của nó. Việc chèn các tập lệnh lớn sẽ tạo ra những vi trễ có thể đo lường được trong giai đoạn document_start. Các hệ thống chống gian lận đo lường khoảng thời gian giữa các sự kiện trình duyệt nội bộ và có thể phát hiện ra những khoảng trống không tự nhiên này.

  2. Vi phạm vòng đời. Các plugin ẩn danh không thể chỉ đơn giản là loại bỏ các cờ tự động hóa; thay vào đó, chúng dựa vào các hook và ghi đè phức tạp. Các hệ thống bảo vệ có thể phát hiện ra rằng các đối tượng proxy phức tạp và các thuộc tính bị ghi đè đã xuất hiện trong đối tượng window bất thường từ rất sớm, trước sự kiện DOMContentLoaded hoặc thậm chí trước khi thẻ <head> được phân tích cú pháp. Điều này phá vỡ vòng đời tự nhiên của trang.

  3. Thiếu sự cô lập. Bất kỳ tập lệnh nào được chèn thông qua lệnh CDP addScriptToEvaluateOnNewDocument đều chạy trong ngữ cảnh thực thi chính của trang. Do đó, mã giả mạo của bạn và các tập lệnh chống gian lận của trang web chia sẻ cùng một môi trường. Một lỗi nhỏ nhất hoặc một biến vô tình bị lộ có thể là đủ để hệ thống bảo vệ phát hiện ra sự tự động hóa.

Bạn không thể từ bỏ hoàn toàn việc giả mạo, nhưng bạn có thể thay đổi cách gửi mã giả mạo. Thay vì chèn mã thông qua giao thức gỡ lỗi, bạn có thể sử dụng hệ thống tiện ích mở rộng gốc của trình duyệt.

Trình duyệt được thiết kế để cho phép các tiện ích mở rộng chèn mã một cách an toàn. Nếu bạn đóng gói logic giả mạo của mình thành một tiện ích mở rộng Manifest V3 và tải nó khi khởi chạy Puppeteer, các hệ thống chống gian lận sẽ coi thời gian và các lần chèn đó là hành vi bình thường của tiện ích mở rộng trình duyệt.

Ví dụ, bạn có thể tạo một thư mục stealth-extension chứa tệp manifest.json:

{
  "manifest_version": 3,
  "name": "My Custom Stealth",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["inject.js"],
      "run_at": "document_start",
      "world": "MAIN"
    }
  ]
}
{
  "manifest_version": 3,
  "name": "My Custom Stealth",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["inject.js"],
      "run_at": "document_start",
      "world": "MAIN"
    }
  ]
}

Và tải nó khi khởi chạy Puppeteer thay vì gọi evaluateOnNewDocument:

const browser = await puppeteer.launch({
  args: [
    `--disable-extensions-except=${pathToExtension}`,
    `--load-extension=${pathToExtension}`
  ]
});
const browser = await puppeteer.launch({
  args: [
    `--disable-extensions-except=${pathToExtension}`,
    `--load-extension=${pathToExtension}`
  ]
});

Cách tiếp cận thứ hai là tránh hoàn toàn các cơ chế chèn V8. Bạn có thể sử dụng một máy chủ proxy bên ngoài hoặc tính năng chặn yêu cầu tích hợp sẵn của Puppeteer để sửa đổi phản hồi HTML thô một cách nhanh chóng.

Chèn thẻ <script> giả mạo của bạn làm dòng đầu tiên bên trong phần tử <head>. Trong kịch bản này, mã thực thi một cách tự nhiên như một phần của quá trình phân tích cú pháp tài liệu tiêu chuẩn của trình duyệt, không gây ra nghi ngờ từ các hệ thống phát hiện dựa trên thời gian.

Nếu bạn phải sử dụng addScriptToEvaluateOnNewDocument, hãy tránh tải các plugin ẩn danh nguyên khối lớn. Thay vào đó, hãy chia quá trình giả mạo thành hai giai đoạn.

Trước khi trang tải, chỉ loại bỏ dấu vết webdriver. Mã này thực thi trong một phần nhỏ của mili giây và không tạo ra các bất thường có thể phát hiện được thông qua Performance API.

// Inject a minimal payload before HTML parsing begins
await page.evaluateOnNewDocument(() => {
    // Remove the most obvious automation marker
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined,
    });
    // No heavy WebGL or Canvas spoofing here!
});
// Inject a minimal payload before HTML parsing begins
await page.evaluateOnNewDocument(() => {
    // Remove the most obvious automation marker
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined,
    });
    // No heavy WebGL or Canvas spoofing here!
});

Tải tất cả các sửa đổi khác—chẳng hạn như giả mạo GPU, âm thanh, plugin hoặc phông chữ—sau đó, sau khi trình duyệt đã bắt đầu kết xuất trang. Việc này có thể được thực hiện thông qua một lệnh gọi page.evaluate() tiêu chuẩn hoặc bằng cách gắn vào sự kiện DOMContentLoaded.

// Navigate to the website
await page.goto('https://target-site.com');

// The page is already loading and timing checks are complete. 
// Now it is safer to inject heavier spoofing logic.
await page.evaluate(() => {
    // Spoof Canvas, WebGL, fonts, etc.
    const getParameter = WebGLRenderingContext.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (parameter === 37445) return 'Intel Inc.';
        if (parameter === 37446) return 'Intel Iris OpenGL Engine';
        return getParameter(parameter);
    };
});
// Navigate to the website
await page.goto('https://target-site.com');

// The page is already loading and timing checks are complete. 
// Now it is safer to inject heavier spoofing logic.
await page.evaluate(() => {
    // Spoof Canvas, WebGL, fonts, etc.
    const getParameter = WebGLRenderingContext.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (parameter === 37445) return 'Intel Inc.';
        if (parameter === 37446) return 'Intel Iris OpenGL Engine';
        return getParameter(parameter);
    };
});

Các hệ thống bảo vệ thường kiểm tra webdriver một cách đồng bộ ngay khi bắt đầu vòng đời của trang, làm cho phương pháp này hiệu quả chống lại các cuộc kiểm tra như vậy. Các cuộc kiểm tra chống gian lận tiên tiến hơn thường chạy bất đồng bộ sau khi trang đã tải xong. Đến thời điểm đó, tập lệnh giả mạo nặng hơn ở giai đoạn hai đã được tải mà không gây ra sự chậm trễ khi khởi động trang.

Vấn đề Network.setUserAgentOverride

Thay đổi User-Agent thông qua CDP rất dễ dàng, nhưng vấn đề là chỉ có tiêu đề HTTP được sửa đổi theo cách này, trong khi các chỉ báo môi trường khác vẫn không thay đổi.

Kết quả là, bạn tạo ra một sự không nhất quán nghiêm trọng. Phương thức Network.setUserAgentOverride không thể giả mạo đúng cách các thuộc tính đối tượng navigator nội bộ hoặc các tiêu đề Client Hints cụ thể.

Một hệ thống phân tích có thể thấy một User-Agent di động trong tiêu đề yêu cầu đồng thời phát hiện hành vi kết xuất API của máy tính để bàn và các tiêu đề sec-ch-ua không khớp.

Không bao giờ chỉ thay đổi chuỗi User-Agent một mình. Nếu bạn đang mô phỏng một thiết bị, bạn phải giả mạo toàn bộ tập hợp Client Hints cũng như các cảm biến và đặc tính phần cứng. Thay vì chỉ dựa vào setUserAgent, hãy sử dụng các tham số CDP mở rộng và cung cấp một đối tượng userAgentMetadata hoàn chỉnh. Ngoài ra, hãy nhớ mô phỏng các hành vi cụ thể của thiết bị, chẳng hạn như mô phỏng màn hình cảm ứng, khi giả danh các thiết bị di động.

Quét cổng TCP gỡ lỗi

Để điều khiển trình duyệt, Puppeteer khởi chạy Chromium với một cổng mở để giao tiếp WebSocket. Theo mặc định, đây thường là một cổng gỡ lỗi cục bộ chẳng hạn như cổng cổ điển 9222 hoặc một cổng khác được gán ngẫu nhiên.

Một tập lệnh chống gian lận chạy trực tiếp bên trong trình duyệt của người dùng. Từ bên trong hệ thống của riêng bạn và thay mặt cho trình duyệt của bạn, nó có thể chỉ đơn giản là đưa ra một yêu cầu AJAX tầm thường như:

http://127.0.0.1:9222/json/version.

Các hệ thống bảo vệ sử dụng các cuộc tấn công dựa trên thời gian (timing attacks) hoặc quét mạng cục bộ thông qua WebSockets. Nếu một tập lệnh kết nối với một cổng gỡ lỗi tiêu chuẩn và ngay lập tức nhận được phản hồi từ công cụ trình duyệt, nó có thể suy ra rằng trình duyệt đang bị điều khiển bởi một tập lệnh tự động hóa.

Ngẫu nhiên hóa cổng không phải là một giải pháp hoàn chỉnh, vì các trình quét có thể thăm dò toàn bộ phạm vi cổng. Giải pháp đơn giản nhất là ngừng hoàn toàn việc sử dụng các cổng TCP để giao tiếp giữa Node.js và trình duyệt.

Puppeteer có thể giao tiếp với Chromium thông qua các đường ống (pipes) hệ điều hành ẩn danh thay vì các socket mạng. Ở chế độ này, không có cổng gỡ lỗi nào được mở, không để lại bất cứ thứ gì cho các hệ thống chống gian lận quét.

Chỉ cần thêm đối số pipe: true khi khởi chạy trình duyệt. Điều này bảo vệ bạn khỏi việc quét mạng host cục bộ.

Đạt được kết quả mà không bị phát hiện

Sự vô hình hoàn hảo của Puppeteer về cơ bản là không thể bởi vì mọi hình thức mô phỏng đều tạo ra một số sai lệch so với hành vi của người dùng thực sự. Như đã chứng minh ở trên, đây đơn giản là một thực tế kỹ thuật.

Đối với các trường hợp sử dụng nghiêm túc, các plugin mặc định không bao giờ là đủ. Để đạt được mức độ ẩn danh cao đòi hỏi một cách tiếp cận hoàn toàn khác:

  1. Vá lỗi tệp nhị phân Chrome. Điều này liên quan đến việc sửa đổi trực tiếp tệp thực thi của trình duyệt, ví dụ bằng trình chỉnh sửa hex. Mục tiêu là thay thế các chuỗi giao thức được mã hóa cứng bằng các giá trị ngẫu nhiên bên trong chính tệp nhị phân. Không giống như các plugin ẩn danh, vốn để lại một cửa sổ lỗ hổng ngắn giữa lúc khởi động và chèn JavaScript, các sửa đổi tệp nhị phân sẽ loại bỏ vấn đề ngay từ nguồn của nó.

  2. Trình duyệt chuyên dụng. Các giải pháp cấp độ kiến trúc chẳng hạn như trình duyệt chống phát hiện triển khai giả mạo vân tay và che giấu tự động hóa bên trong cơ sở mã C++ của công cụ Blink thay vì thông qua việc chèn JavaScript.

  3. Tránh hoàn toàn CDP. Logic điều khiển có thể được chuyển vào các tiện ích mở rộng Chrome tùy chỉnh để giao tiếp với máy chủ điều khiển thông qua các kênh WebSocket của riêng chúng, cho phép bạn đóng các cổng gỡ lỗi.

Sử dụng trình duyệt chống phát hiện

Các giải pháp chuyên dụng như trình duyệt chống phát hiện xứng đáng được chú ý riêng. Chúng giải quyết vấn đề đang bàn bằng cách sửa đổi mã nguồn của Chromium, ảnh hưởng trực tiếp đến nhân trình duyệt. Các chỉ báo tự động hóa rõ ràng như navigator.webdriver, dĩ nhiên, được loại bỏ trong quá trình biên dịch tệp nhị phân thay vì bị ẩn sau đó.

Tất cả các công việc nặng nhọc liên quan đến mô phỏng môi trường đều diễn ra dưới mui xe. Khi một tập lệnh bảo vệ yêu cầu thông tin GPU, chẳng hạn như giá trị nhà cung cấp WebGL hoặc bộ kết xuất, công cụ không cần phải thực thi một trình bao bọc JavaScript để giả mạo chúng, vì mã C++ sẽ trả về các giá trị yêu cầu một cách tự nhiên và tức thì. Điều tương tự cũng áp dụng cho các chỉ số phức tạp hơn. Hơn thế nữa, người dùng không tương tác trực tiếp với bất kỳ cơ chế nào trong số này, ngoại trừ có thể là để định cấu hình các tham số cần thiết trước khi khởi chạy một hồ sơ.

Dưới góc nhìn của một hệ thống chống gian lận, một trình duyệt như vậy có vẻ là một người dùng bình thường. Đồng thời, bạn có thể kiểm soát vân tay thông qua Puppeteer đơn giản bằng cách kết nối nó với cổng mở của trình duyệt chống phát hiện.

Các chi tiết ít rõ ràng hơn và kết luận

  • Sử dụng đối số --disable-blink-features=AutomationControlled loại bỏ dấu vết webdriver cơ bản nhưng vẫn để lại các dấu vết gián tiếp phía sau.

  • Buộc Log.enablePerformance.enable tiếp tục bị vô hiệu hóa giúp giảm lượng dữ liệu đo lường từ xa có sẵn nhưng cải thiện khả năng ẩn danh tổng thể.

  • Chế độ Headless mới (--headless=new) thống nhất kiến trúc của các trình duyệt tiêu chuẩn và không đầu (headless), thay đổi cách phát hiện ClientRects và kết xuất phông chữ, đồng thời làm cho hành vi kết xuất trông tự nhiên hơn.

Vượt qua sự phát hiện thành công không bắt đầu bằng việc cài đặt một trăm gói npm. Nó bắt đầu bằng việc hiểu cách công cụ trình duyệt thực sự hoạt động.

Tự động hóa trình duyệt hiện đại đòi hỏi độ chính xác ở cấp độ kỹ thuật. Rò rỉ CDP không phải là lỗi; chúng là những đặc tính kiến trúc của công nghệ. Hiểu biết của bạn về trình duyệt càng sâu sắc, bạn càng có nhiều quyền kiểm soát đối với mọi ngữ cảnh thực thi, và các hệ thống tự động hóa của bạn càng trở nên kiên cố hơn.

Duy trì danh tính ẩn trực tuyến của bạn với Octo Browser. Dấu vân tay số thật của bạn sẽ không thể bị truy vết.

CDP là gì, và tại sao nó lại để lại dấu vết kỹ thuật số?

Giao thức Chrome DevTools (CDP) cung cấp quyền truy cập cấp thấp vào kiến trúc của trình duyệt. Ban đầu nó được thiết kế để gỡ lỗi, kiểm tra và phân tích hiệu năng, chứ không phải để thu thập dữ liệu ẩn danh.

Giao thức này hoạt động thông qua các kết nối WebSocket tương tác trực tiếp với công cụ V8. Khi tập lệnh của bạn gửi một lệnh, chẳng hạn như nhấp chuột hoặc điều hướng trang, trình duyệt sẽ mở một socket cục bộ để giao tiếp hai chiều. Quá trình này chắc chắn sẽ tạo ra các nhật ký nội bộ, ảnh hưởng đến việc phân bổ bộ nhớ và để lại các tạo tác bên trong môi trường trình duyệt bị cô lập. Các hệ thống bảo mật có thể phân tích các thay đổi vi mô và mô hình thời gian này để phát hiện hành vi tự động. Do đó, chỉ riêng sự tồn tại của kết nối cũng có thể tiết lộ sự tự động hóa trình duyệt.

Sự khác biệt giữa rò lỉ CDP và dấu vân tay truyền thống

Điều quan trọng là phải hiểu rằng rò rỉ CDP và rò lỉ vân tay trình duyệt đại diện cho hai vectơ phát hiện hoàn toàn khác nhau.

  • Dấu vân tay truyền thống (Canvas, WebGL, Fonts) xác định các đặc tính phần cứng độc nhất và hành vi kết xuất. Sự không nhất quán của vân tay hoặc lỗi giả mạo cho phép các hệ thống chống bot phát hiện thao túng và phân loại người dùng là đáng ngờ.

  • Rò rỉ CDP tiết lộ các chỉ báo cấu trúc và hành vi của quá trình thực thi mã. Chúng phơi bày thực tế rằng trình duyệt đang bị điều khiển từ xa.

Bạn có thể có một vân tay WebGL độc nhất hoàn hảo, nhưng các cờ tự động hóa có thể ngay lập tức làm mất hiệu lực lợi thế đó. Việc kiểm tra vân tay trình duyệt đòi hỏi tài nguyên và thời gian xử lý đáng kể, trong khi việc kiểm tra các biến môi trường hoặc ngăn xếp cuộc gọi hầu như là miễn phí. Đây chính là lý do tại sao các hệ thống chống gian lận ưa chuộng phát hiện dựa trên CDP. Nó cho phép họ lọc bỏ các bot trong quá trình kiểm tra cấp giao thức cơ bản, giúp tiết kiệm tài nguyên máy chủ vốn phải chi cho phân tích hành vi chuyên sâu tốn kém hơn.

Giải phẫu rò rỉ Giao thức Chrome DevTools

Các dấu vết của Ngữ cảnh Thực thi (Execution Context)

Một trong những điểm yếu chính của Puppeteer nằm ở cách chính xác mà nó thực thi mã của bạn bên trong trình duyệt. Dưới mui xe, phương thức page.evaluate() dựa vào lệnh CDP Runtime.evaluate.

Khi một tập lệnh được chèn vào trình duyệt, các phiên bản Puppeteer hiện đại sẽ tạo ra một URL Nguồn cụ thể liên kết với phân đoạn mã đó. Nếu xảy ra lỗi trong quá trình thực thi tập lệnh, công cụ V8 sẽ tạo ra một dấu vết ngăn xếp (stack trace) tiêu chuẩn có thể chứa các mục như:

at pptr:evaluate;C:\Users\Admin\Projects\...\main.js:17:14

Bằng cách ghi đè các hàm trình duyệt cơ bản, các hệ thống chống gian lận có thể cố ý kích hoạt các lỗi vô hình và kiểm tra đối tượng Error.stack. Họ có thể phát hiện ra:

  • Tiền tố pptr:, một tham chiếu trực tiếp đến Puppeteer.

  • Một đường dẫn tuyệt đối đến một tệp trên máy cục bộ hoặc máy chủ của bạn, bắt đầu bằng C:\ hoặc /var/www/....

Trình duyệt của một người dùng thực sự không bao giờ thực thi mã từ các đường dẫn hệ thống tệp cục bộ khi truy cập một trang web công cộng. Do đó, các dấu vết như vậy đại diện cho bằng chứng xác thực của sự tự động hóa.

Execution Context markers

Thay đổi địa chỉ IP hoặc giả mạo vân tay Canvas sẽ không giải quyết được vấn đề này. Để ẩn các dấu vết này, bạn phải sửa đổi chính thư viện hoặc môi trường thực thi trình duyệt.

Giải pháp đáng tin cậy nhất là xóa trực tiếp dấu vết pptr:evaluate khỏi mã nguồn của Puppeteer trước khi các lệnh được gửi đến trình duyệt. Vì việc tìm kiếm và chỉnh sửa thủ công các tệp sau mỗi lần cài đặt npm là không thực tế, một trình vá lỗi đơn giản có thể được sử dụng:

const fs = require('fs');
const path = require('path');

// Path to ExecutionContext.js in recent Puppeteer versions
const targetFile = path.resolve(__dirname, 'node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js');

if (fs.existsSync(targetFile)) {
    let content = fs.readFileSync(targetFile, 'utf8');
    // Replace the pptr:evaluate prefix with an anonymous call
    // and remove the local file path
    const patchedContent = content.replace(/pptr:evaluate;.*?\\n/g, 'anonymous:evaluation;\n');
    fs.writeFileSync(targetFile, patchedContent, 'utf8');
    console.log('Puppeteer patched successfully. pptr:evaluate markers removed.');
}

Nếu việc sửa đổi node_modules không phải là một lựa chọn, các dấu vết có thể được lọc tại thời điểm chạy bằng cách chèn một tập lệnh giả mạo trước khi trang mục tiêu tải. Tập lệnh này ghi đè hành vi của đối tượng Error gốc và làm sạch các dấu vết ngăn xếp:

await page.evaluateOnNewDocument(() => {
    // Save the original Error constructor
    const NativeError = window.Error;

    window.Error = function(...args) {
        const err = new NativeError(...args);
        const originalStack = err.stack;

        if (originalStack) {
            Object.defineProperty(err, 'stack', {
                get: function() {
                    // Break stack trace into strings and remove Puppeteer-related entries from it
                    return originalStack
                        .split('\n')
                        .filter(line => !line.includes('pptr:evaluate'))
                        .join('\n');
                }
            });
        }
        return err;
    };
    
    // Restore the prototype chain to avoid detection
    window.Error.prototype = NativeError.prototype;
});
As a result, instead of exposing local file paths, the stack trace will display something like this

Kết quả là, thay vì để lộ các đường dẫn tệp cục bộ, dấu vết ngăn xếp sẽ hiển thị một cái gì đó như thế này

Lỗ hổng của Page.addScriptToEvaluateOnNewDocument

Các nỗ lực nhằm che giấu sự tự động hóa trình duyệt, bao gồm cả việc sử dụng các plugin ẩn danh phổ biến, thường dựa vào việc chèn JavaScript giả mạo trước khi trang web mục tiêu tải. Trong Puppeteer, việc này thường được thực hiện thông qua lệnh Page.addScriptToEvaluateOnNewDocument.

Tuy nhiên, bản thân việc sử dụng phương thức này lại để lại những dấu vết rõ rệt trong môi trường thực thi.

  1. Sự bất thường về thời gian. Lệnh này buộc công cụ V8 phải thực thi mã của bạn một cách đồng bộ khi ngữ cảnh trang được tạo, trước khi trình phân tích cú pháp HTML bắt đầu công việc của nó. Việc chèn các tập lệnh lớn sẽ tạo ra những vi trễ có thể đo lường được trong giai đoạn document_start. Các hệ thống chống gian lận đo lường khoảng thời gian giữa các sự kiện trình duyệt nội bộ và có thể phát hiện ra những khoảng trống không tự nhiên này.

  2. Vi phạm vòng đời. Các plugin ẩn danh không thể chỉ đơn giản là loại bỏ các cờ tự động hóa; thay vào đó, chúng dựa vào các hook và ghi đè phức tạp. Các hệ thống bảo vệ có thể phát hiện ra rằng các đối tượng proxy phức tạp và các thuộc tính bị ghi đè đã xuất hiện trong đối tượng window bất thường từ rất sớm, trước sự kiện DOMContentLoaded hoặc thậm chí trước khi thẻ <head> được phân tích cú pháp. Điều này phá vỡ vòng đời tự nhiên của trang.

  3. Thiếu sự cô lập. Bất kỳ tập lệnh nào được chèn thông qua lệnh CDP addScriptToEvaluateOnNewDocument đều chạy trong ngữ cảnh thực thi chính của trang. Do đó, mã giả mạo của bạn và các tập lệnh chống gian lận của trang web chia sẻ cùng một môi trường. Một lỗi nhỏ nhất hoặc một biến vô tình bị lộ có thể là đủ để hệ thống bảo vệ phát hiện ra sự tự động hóa.

Bạn không thể từ bỏ hoàn toàn việc giả mạo, nhưng bạn có thể thay đổi cách gửi mã giả mạo. Thay vì chèn mã thông qua giao thức gỡ lỗi, bạn có thể sử dụng hệ thống tiện ích mở rộng gốc của trình duyệt.

Trình duyệt được thiết kế để cho phép các tiện ích mở rộng chèn mã một cách an toàn. Nếu bạn đóng gói logic giả mạo của mình thành một tiện ích mở rộng Manifest V3 và tải nó khi khởi chạy Puppeteer, các hệ thống chống gian lận sẽ coi thời gian và các lần chèn đó là hành vi bình thường của tiện ích mở rộng trình duyệt.

Ví dụ, bạn có thể tạo một thư mục stealth-extension chứa tệp manifest.json:

{
  "manifest_version": 3,
  "name": "My Custom Stealth",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["inject.js"],
      "run_at": "document_start",
      "world": "MAIN"
    }
  ]
}

Và tải nó khi khởi chạy Puppeteer thay vì gọi evaluateOnNewDocument:

const browser = await puppeteer.launch({
  args: [
    `--disable-extensions-except=${pathToExtension}`,
    `--load-extension=${pathToExtension}`
  ]
});

Cách tiếp cận thứ hai là tránh hoàn toàn các cơ chế chèn V8. Bạn có thể sử dụng một máy chủ proxy bên ngoài hoặc tính năng chặn yêu cầu tích hợp sẵn của Puppeteer để sửa đổi phản hồi HTML thô một cách nhanh chóng.

Chèn thẻ <script> giả mạo của bạn làm dòng đầu tiên bên trong phần tử <head>. Trong kịch bản này, mã thực thi một cách tự nhiên như một phần của quá trình phân tích cú pháp tài liệu tiêu chuẩn của trình duyệt, không gây ra nghi ngờ từ các hệ thống phát hiện dựa trên thời gian.

Nếu bạn phải sử dụng addScriptToEvaluateOnNewDocument, hãy tránh tải các plugin ẩn danh nguyên khối lớn. Thay vào đó, hãy chia quá trình giả mạo thành hai giai đoạn.

Trước khi trang tải, chỉ loại bỏ dấu vết webdriver. Mã này thực thi trong một phần nhỏ của mili giây và không tạo ra các bất thường có thể phát hiện được thông qua Performance API.

// Inject a minimal payload before HTML parsing begins
await page.evaluateOnNewDocument(() => {
    // Remove the most obvious automation marker
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined,
    });
    // No heavy WebGL or Canvas spoofing here!
});

Tải tất cả các sửa đổi khác—chẳng hạn như giả mạo GPU, âm thanh, plugin hoặc phông chữ—sau đó, sau khi trình duyệt đã bắt đầu kết xuất trang. Việc này có thể được thực hiện thông qua một lệnh gọi page.evaluate() tiêu chuẩn hoặc bằng cách gắn vào sự kiện DOMContentLoaded.

// Navigate to the website
await page.goto('https://target-site.com');

// The page is already loading and timing checks are complete. 
// Now it is safer to inject heavier spoofing logic.
await page.evaluate(() => {
    // Spoof Canvas, WebGL, fonts, etc.
    const getParameter = WebGLRenderingContext.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (parameter === 37445) return 'Intel Inc.';
        if (parameter === 37446) return 'Intel Iris OpenGL Engine';
        return getParameter(parameter);
    };
});

Các hệ thống bảo vệ thường kiểm tra webdriver một cách đồng bộ ngay khi bắt đầu vòng đời của trang, làm cho phương pháp này hiệu quả chống lại các cuộc kiểm tra như vậy. Các cuộc kiểm tra chống gian lận tiên tiến hơn thường chạy bất đồng bộ sau khi trang đã tải xong. Đến thời điểm đó, tập lệnh giả mạo nặng hơn ở giai đoạn hai đã được tải mà không gây ra sự chậm trễ khi khởi động trang.

Vấn đề Network.setUserAgentOverride

Thay đổi User-Agent thông qua CDP rất dễ dàng, nhưng vấn đề là chỉ có tiêu đề HTTP được sửa đổi theo cách này, trong khi các chỉ báo môi trường khác vẫn không thay đổi.

Kết quả là, bạn tạo ra một sự không nhất quán nghiêm trọng. Phương thức Network.setUserAgentOverride không thể giả mạo đúng cách các thuộc tính đối tượng navigator nội bộ hoặc các tiêu đề Client Hints cụ thể.

Một hệ thống phân tích có thể thấy một User-Agent di động trong tiêu đề yêu cầu đồng thời phát hiện hành vi kết xuất API của máy tính để bàn và các tiêu đề sec-ch-ua không khớp.

Không bao giờ chỉ thay đổi chuỗi User-Agent một mình. Nếu bạn đang mô phỏng một thiết bị, bạn phải giả mạo toàn bộ tập hợp Client Hints cũng như các cảm biến và đặc tính phần cứng. Thay vì chỉ dựa vào setUserAgent, hãy sử dụng các tham số CDP mở rộng và cung cấp một đối tượng userAgentMetadata hoàn chỉnh. Ngoài ra, hãy nhớ mô phỏng các hành vi cụ thể của thiết bị, chẳng hạn như mô phỏng màn hình cảm ứng, khi giả danh các thiết bị di động.

Quét cổng TCP gỡ lỗi

Để điều khiển trình duyệt, Puppeteer khởi chạy Chromium với một cổng mở để giao tiếp WebSocket. Theo mặc định, đây thường là một cổng gỡ lỗi cục bộ chẳng hạn như cổng cổ điển 9222 hoặc một cổng khác được gán ngẫu nhiên.

Một tập lệnh chống gian lận chạy trực tiếp bên trong trình duyệt của người dùng. Từ bên trong hệ thống của riêng bạn và thay mặt cho trình duyệt của bạn, nó có thể chỉ đơn giản là đưa ra một yêu cầu AJAX tầm thường như:

http://127.0.0.1:9222/json/version.

Các hệ thống bảo vệ sử dụng các cuộc tấn công dựa trên thời gian (timing attacks) hoặc quét mạng cục bộ thông qua WebSockets. Nếu một tập lệnh kết nối với một cổng gỡ lỗi tiêu chuẩn và ngay lập tức nhận được phản hồi từ công cụ trình duyệt, nó có thể suy ra rằng trình duyệt đang bị điều khiển bởi một tập lệnh tự động hóa.

Ngẫu nhiên hóa cổng không phải là một giải pháp hoàn chỉnh, vì các trình quét có thể thăm dò toàn bộ phạm vi cổng. Giải pháp đơn giản nhất là ngừng hoàn toàn việc sử dụng các cổng TCP để giao tiếp giữa Node.js và trình duyệt.

Puppeteer có thể giao tiếp với Chromium thông qua các đường ống (pipes) hệ điều hành ẩn danh thay vì các socket mạng. Ở chế độ này, không có cổng gỡ lỗi nào được mở, không để lại bất cứ thứ gì cho các hệ thống chống gian lận quét.

Chỉ cần thêm đối số pipe: true khi khởi chạy trình duyệt. Điều này bảo vệ bạn khỏi việc quét mạng host cục bộ.

Đạt được kết quả mà không bị phát hiện

Sự vô hình hoàn hảo của Puppeteer về cơ bản là không thể bởi vì mọi hình thức mô phỏng đều tạo ra một số sai lệch so với hành vi của người dùng thực sự. Như đã chứng minh ở trên, đây đơn giản là một thực tế kỹ thuật.

Đối với các trường hợp sử dụng nghiêm túc, các plugin mặc định không bao giờ là đủ. Để đạt được mức độ ẩn danh cao đòi hỏi một cách tiếp cận hoàn toàn khác:

  1. Vá lỗi tệp nhị phân Chrome. Điều này liên quan đến việc sửa đổi trực tiếp tệp thực thi của trình duyệt, ví dụ bằng trình chỉnh sửa hex. Mục tiêu là thay thế các chuỗi giao thức được mã hóa cứng bằng các giá trị ngẫu nhiên bên trong chính tệp nhị phân. Không giống như các plugin ẩn danh, vốn để lại một cửa sổ lỗ hổng ngắn giữa lúc khởi động và chèn JavaScript, các sửa đổi tệp nhị phân sẽ loại bỏ vấn đề ngay từ nguồn của nó.

  2. Trình duyệt chuyên dụng. Các giải pháp cấp độ kiến trúc chẳng hạn như trình duyệt chống phát hiện triển khai giả mạo vân tay và che giấu tự động hóa bên trong cơ sở mã C++ của công cụ Blink thay vì thông qua việc chèn JavaScript.

  3. Tránh hoàn toàn CDP. Logic điều khiển có thể được chuyển vào các tiện ích mở rộng Chrome tùy chỉnh để giao tiếp với máy chủ điều khiển thông qua các kênh WebSocket của riêng chúng, cho phép bạn đóng các cổng gỡ lỗi.

Sử dụng trình duyệt chống phát hiện

Các giải pháp chuyên dụng như trình duyệt chống phát hiện xứng đáng được chú ý riêng. Chúng giải quyết vấn đề đang bàn bằng cách sửa đổi mã nguồn của Chromium, ảnh hưởng trực tiếp đến nhân trình duyệt. Các chỉ báo tự động hóa rõ ràng như navigator.webdriver, dĩ nhiên, được loại bỏ trong quá trình biên dịch tệp nhị phân thay vì bị ẩn sau đó.

Tất cả các công việc nặng nhọc liên quan đến mô phỏng môi trường đều diễn ra dưới mui xe. Khi một tập lệnh bảo vệ yêu cầu thông tin GPU, chẳng hạn như giá trị nhà cung cấp WebGL hoặc bộ kết xuất, công cụ không cần phải thực thi một trình bao bọc JavaScript để giả mạo chúng, vì mã C++ sẽ trả về các giá trị yêu cầu một cách tự nhiên và tức thì. Điều tương tự cũng áp dụng cho các chỉ số phức tạp hơn. Hơn thế nữa, người dùng không tương tác trực tiếp với bất kỳ cơ chế nào trong số này, ngoại trừ có thể là để định cấu hình các tham số cần thiết trước khi khởi chạy một hồ sơ.

Dưới góc nhìn của một hệ thống chống gian lận, một trình duyệt như vậy có vẻ là một người dùng bình thường. Đồng thời, bạn có thể kiểm soát vân tay thông qua Puppeteer đơn giản bằng cách kết nối nó với cổng mở của trình duyệt chống phát hiện.

Các chi tiết ít rõ ràng hơn và kết luận

  • Sử dụng đối số --disable-blink-features=AutomationControlled loại bỏ dấu vết webdriver cơ bản nhưng vẫn để lại các dấu vết gián tiếp phía sau.

  • Buộc Log.enablePerformance.enable tiếp tục bị vô hiệu hóa giúp giảm lượng dữ liệu đo lường từ xa có sẵn nhưng cải thiện khả năng ẩn danh tổng thể.

  • Chế độ Headless mới (--headless=new) thống nhất kiến trúc của các trình duyệt tiêu chuẩn và không đầu (headless), thay đổi cách phát hiện ClientRects và kết xuất phông chữ, đồng thời làm cho hành vi kết xuất trông tự nhiên hơn.

Vượt qua sự phát hiện thành công không bắt đầu bằng việc cài đặt một trăm gói npm. Nó bắt đầu bằng việc hiểu cách công cụ trình duyệt thực sự hoạt động.

Tự động hóa trình duyệt hiện đại đòi hỏi độ chính xác ở cấp độ kỹ thuật. Rò rỉ CDP không phải là lỗi; chúng là những đặc tính kiến trúc của công nghệ. Hiểu biết của bạn về trình duyệt càng sâu sắc, bạn càng có nhiều quyền kiểm soát đối với mọi ngữ cảnh thực thi, và các hệ thống tự động hóa của bạn càng trở nên kiên cố hơn.

Cập nhật với các tin tức Octo Browser mới nhất

Khi nhấp vào nút này, bạn sẽ đồng ý với Chính sách Quyền riêng tư của chúng tôi.

Cập nhật với các tin tức Octo Browser mới nhất

Khi nhấp vào nút này, bạn sẽ đồng ý với Chính sách Quyền riêng tư của chúng tôi.

Cập nhật với các tin tức Octo Browser mới nhất

Khi nhấp vào nút này, bạn sẽ đồng ý với Chính sách Quyền riêng tư của chúng tôi.

Tham gia Octo Browser ngay

Hoặc liên hệ với Dịch vụ khách hàng bất kì lúc nào nếu bạn có bất cứ thắc mắc nào.

Tham gia Octo Browser ngay

Hoặc liên hệ với Dịch vụ khách hàng bất kì lúc nào nếu bạn có bất cứ thắc mắc nào.

Tham gia Octo Browser ngay

Hoặc liên hệ với Dịch vụ khách hàng bất kì lúc nào nếu bạn có bất cứ thắc mắc nào.

©

2026

Octo Browser

©

2026

Octo Browser

©

2026

Octo Browser