Cách website phát hiện Puppeteer, Playwright và Selenium qua các hiệu ứng phụ của CDP, gồm rò rỉ tuần tự hóa ở Runtime.enable.
Puppeteer, Playwright, và phần lớn Selenium hiện đại đều điều khiển Chrome qua cùng một kênh cấp thấp: Chrome DevTools Protocol (CDP) — giao diện vận hành chính Chrome DevTools. Nền tảng dùng chung này rất tiện cho tác giả công cụ tự động hóa, nhưng cũng có nghĩa mỗi công cụ trong số đó có thể để lại cùng một nhóm dấu vết — bất kể chuỗi user-agent hay navigator.webdriver có bị giả mạo hay không. Bài viết này giải thích CDP là gì, những hiệu ứng phụ cụ thể mà nó tạo ra, và cách các hệ thống phát hiện biến những hiệu ứng phụ đó thành một tín hiệu.
Điểm chính
- CDP là kênh điều khiển dùng chung đằng sau Puppeteer, driver Chromium của Playwright, và các phiên Selenium dựa trên CDP — một bộ phát hiện nhắm vào chính CDP có thể bắt được cả ba cùng lúc.
- Bật domain Runtime tạo ra một hiệu ứng phụ có thể quan sát được: một khi client gửi
Runtime.enable, mọi lệnh gọiconsole.*đều bị chặn và các tham số của nó được tuần tự hóa để tạo bản xem trước, việc này chủ động kích hoạt các getter thuộc tính mà trang lẽ ra sẽ không bao giờ chạm tới. - Hiệu ứng phụ tuần tự hóa đó là một nguyên hàm phát hiện: một trang có thể định nghĩa getter trên giá trị truyền vào
console.logvà kiểm tra xem nó có kích hoạt ngay lập tức hay không — từ đó lộ ra một client CDP đang kết nối mà không cần hiển thị bất kỳ log nào. - Các framework khác nhau về mức độ lộ diện: chế độ Chromium mặc định của Puppeteer và Playwright bật
Runtime.enableđể theo dõi execution context; các bản đã vá tránh làm vậy bằng cách quản lý context ID theo cách khác. - Đây chỉ là một tín hiệu trong một hệ thống phát hiện nhiều lớp, không phải viên đạn bạc — nó bổ trợ cho các kiểm tra
navigator.webdrivervà bất thường kết xuất đã nêu trong hướng dẫn phát hiện trình duyệt headless của chúng tôi.
CDP là gì và vì sao tự động hóa phụ thuộc vào nó
Chrome DevTools Protocol là giao diện JSON-RPC cho phép một client bên ngoài kiểm tra và điều khiển một instance Chromium đang chạy: thực thi JavaScript, chặn network request, chụp ảnh màn hình, và nhận các sự kiện console cùng exception. Đây chính là giao thức mà DevTools dùng để giao tiếp với trình duyệt khi bạn tự mở nó lên.
Backend Chromium của Puppeteer và Playwright kết nối trực tiếp qua CDP. Selenium truyền thống dùng giao thức dây W3C WebDriver, nhưng bản triển khai WebDriver của Chromium (chromedriver) tự thân nó cũng là một client CDP bên dưới, và API DevTools đặc thù cho Chromium của Selenium 4 phơi bày CDP trực tiếp. Vì cả ba cuối cùng đều dùng chung một bề mặt điều khiển, một bộ phát hiện nhắm vào cơ chế CDP — thay vì một dấu vết đặc thù của từng framework — có cơ hội bắt được cả ba chỉ bằng một phép kiểm tra.
Các tín hiệu CDP mà một trang có thể quan sát
Một trang web không có API trực tiếp để hỏi "có client CDP nào đang kết nối không?". CDP được thiết kế để vô hình với JavaScript của trang. Nhưng việc kết nối một client làm thay đổi những gì xảy ra bên trong renderer theo những cách mà trang có thể đo được gián tiếp: một số domain của giao thức chỉ phát ra sự kiện khi có client yêu cầu, và việc xử lý các sự kiện đó chạy thêm mã trên cùng luồng JavaScript mà trang đang thực thi. Hiệu ứng hữu ích nhất trong số đó hóa ra chính là hiệu ứng xảy ra khi bật domain Runtime.
Runtime.enable và rò rỉ tuần tự hóa console
Các client tự động hóa gửi Runtime.enable để nhận các sự kiện execution context và console-API-call — Puppeteer và Playwright cần điều này để biết nên thực thi script trong context ID nào. Một khi domain đó được kích hoạt, inspector của V8 sẽ chặn mọi lệnh gọi console.* trước khi nó chạm tới console hiển thị, và xây dựng bản xem trước đối tượng cho sự kiện CDP tương ứng, bất kể có con người nào mở DevTools để xem hay không.
Việc xây dựng bản xem trước đó đòi hỏi phải duyệt qua các thuộc tính của tham số, mà duyệt thuộc tính nghĩa là kích hoạt getter của chúng. Trên một trang không bị can thiệp, getter trên một đối tượng được log qua console.log chỉ kích hoạt nếu con người thực sự mở DevTools và đang nhìn vào. Khi Runtime.enable đang hoạt động, nó kích hoạt vô điều kiện, ngay lập tức, như một hiệu ứng phụ của chính pipeline sự kiện CDP:
// Chỉ chạy ngay lập tức (trước khi bất kỳ output nào xuất hiện ở đâu đó)
// khi một client CDP đã bật domain Runtime.
let cdpDetected = false;
const probe = {};
Object.defineProperty(probe, 'flag', {
get() {
cdpDetected = true;
return 'x';
},
});
console.log(probe);
// Kiểm tra cdpDetected ở tick kế tiếp — không cần console hiển thị nào.
Đây là một kênh phụ về thời điểm/tuần tự hóa, không phải một API có tài liệu chính thức: nó phụ thuộc vào cách inspector của V8 xây dựng bản xem trước console, đó là chi tiết triển khai chứ không phải một hợp đồng ổn định. Đó cũng là lý do vì sao nó chưa từng là một điểm hở vĩnh viễn — hành vi ở cấp engine quanh chính cơ chế chặn này đã thay đổi theo thời gian khi Chromium điều chỉnh cách inspector xử lý tham số console.
Dấu vết của Puppeteer & Playwright
Cả hai framework đều bật domain Runtime trong cấu hình mặc định, đó chính là lý do khiến kỹ thuật này hiệu quả rộng rãi với các script "ngay khi cài đặt" chứ không phải một trường hợp biên hiếm gặp.
| Công cụ | Hành vi CDP mặc định | Cách giảm thiểu phổ biến |
|---|---|---|
| Puppeteer | Gửi Runtime.enable để theo dõi execution context | Các fork cộng đồng tránh giữ domain này luôn bật, khôi phục context ID theo cách khác |
| Playwright (Chromium) | Bật Runtime cho mỗi execution context trong page world mặc định của nó | Các bản vá cộng đồng chạy script tự động hóa trong các world cô lập lấy được mà không cần Runtime.enable |
Selenium (chromedriver / W3C) | Chủ yếu dùng giao thức dây W3C WebDriver; việc dùng CDP hẹp hơn và là tùy chọn | Tránh dùng API DevTools sẽ né được chính phép kiểm tra này |
Kết quả thực tế: một bộ phát hiện xây dựng quanh cơ chế duy nhất này từng tách được các phiên Puppeteer/Playwright mặc định khỏi các phiên Selenium thuần WebDriver, cho tới khi tác giả các công cụ tự động hóa điều chỉnh cách công cụ của họ quản lý CDP.
Heuristic phát hiện đối đầu với né tránh
Đây là động lực mèo vờn chuột giống hệt trong hướng dẫn kỹ thuật phát hiện bot của chúng tôi, chỉ diễn ra ở một tầng thấp hơn trong stack. Ngay khi kênh phụ Runtime.enable được biết đến, nó lan nhanh qua các nhà cung cấp bot-management vì nó bắt được nhiều framework chỉ bằng một phép kiểm tra và không phụ thuộc vào các bản vá tầng JavaScript mà stealth plugin đã xử lý sẵn. Người bảo trì các công cụ tự động hóa đáp lại bằng các bản build bỏ qua Runtime.enable hoặc tái cấu trúc cách theo dõi execution context, bịt lỗ hổng cụ thể đó trong khi giữ nguyên phần hành vi còn lại của framework — cùng một khuôn mẫu thấy được trong một issue GitHub của Puppeteer đã kéo dài nhiều năm, theo dõi các báo cáo phát hiện tự động hóa nhắm vào thư viện này.
Không bên nào giữ được lợi thế lâu dài. Một tín hiệu phụ thuộc vào nội bộ inspector chưa có tài liệu chính là kiểu thứ mà một thay đổi engine có thể âm thầm phá vỡ hoặc khôi phục, đó là lý do các hệ thống phát hiện nghiêm túc coi nó là một đầu vào trong một điểm số lớn hơn — cùng với navigator.webdriver, các bất thường kết xuất WebGL, và dấu vân tay tầng mạng được mô tả ở nơi khác trên trang này — thay vì một phán quyết độc lập.
Kiểm tra thiết lập tự động hóa của chính bạn
Nếu bạn duy trì các bài test trình duyệt CI, kiểm toán trợ năng, hoặc script giám sát, việc biết hệ thống phát hiện nhìn thấy gì về phiên của bạn là điều đáng làm. Công cụ phát hiện bot của BrowserInsight phơi bày navigator.webdriver, các dấu vết headless và tín hiệu tự động hóa khác mà trình duyệt của bạn đang hiển thị hiện tại, và kiểm tra nhân trình duyệt xác nhận engine kết xuất nào thực sự đang chạy bên dưới. Không công cụ nào trong số này kiểm tra trực tiếp lưu lượng giao thức CDP — việc đó diễn ra ở phía máy chủ — nhưng chúng cho thấy các tín hiệu liền kề mà một hệ thống phát hiện thật sự sẽ kết hợp cùng với nó.
Câu hỏi thường gặp
Website có thể phát hiện trực tiếp việc CDP đang kết nối không?
Không thể qua một API có tài liệu chính thức — CDP được thiết kế để vô hình với JavaScript của trang. Điều một trang có thể phát hiện là các hiệu ứng phụ của việc một domain CDP cụ thể đang hoạt động, chẳng hạn hành vi tuần tự hóa console xuất hiện khi domain Runtime được bật.
Điều này có ảnh hưởng đến Selenium giống như Puppeteer và Playwright không?
Không nhất quán bằng. Đường đi cổ điển của Selenium chạy trên giao thức dây W3C WebDriver thay vì trực tiếp qua CDP, nên nó không tự động kích hoạt các hiệu ứng phụ của domain CDP theo cách mà cấu hình Chromium mặc định của Puppeteer và Playwright làm. Các phiên Selenium dùng rõ ràng API DevTools của Chromium vẫn có thể kích hoạt cùng các phép kiểm tra đó.
Việc bật Runtime.enable có phải là một sai lầm của tác giả công cụ tự động hóa không?
Không — đó là cách trực tiếp nhất để nhận các sự kiện execution context và console qua CDP, và đó chính xác là điều DevTools tự làm khi một con người mở nó. Hiệu ứng phụ chỉ trở thành tín hiệu bot vì một script, không giống con người, không có lý do nào khác để có một client CDP kết nối trong môi trường production.
Việc sửa tín hiệu này có khiến tự động hóa trở nên không thể phát hiện không?
Không. Nó chỉ loại bỏ một đầu vào khỏi điểm số phát hiện, vốn còn bao gồm sự không nhất quán trong kết xuất, phân tích hành vi, và dấu vân tay tầng mạng — xem hướng dẫn của chúng tôi về phát hiện trình duyệt headless và kỹ thuật phát hiện bot để biết phần còn lại của hệ thống đó.


