Trình duyệt chống phát hiện là bài toán nhất quán: đối chiếu User-Agent, Client Hints, TLS, múi giờ và ngôn ngữ
Một trang echo TLS hiện ra nhiều hơn là chứng chỉ. Cùng với dữ liệu handshake, nó in ra chuỗi User-Agent, các header Sec-CH-UA mặc định, Accept-Language, chuỗi JA4 của chính gói ClientHello vừa gửi, rồi đến dấu vân tay HTTP/2 và vài đặc trưng của gói TCP. Server chẳng cần đoán. Những thứ ấy nằm trong một kết nối, do bốn tầng khác nhau của cùng một tiến trình sinh ra, và có thể được đọc chéo với nhau ngay lập tức.
Cách làm quen thuộc là đi từng ô một: đổi User-Agent, đổi ngôn ngữ, đổi múi giờ, rồi chạy vài trang kiểm tra thấy xanh là coi như xong. Cách đó bỏ qua một chi tiết: mỗi ô còn có quan hệ với vài ô khác, và quan hệ ấy không nằm trong cùng một tầng. Thứ cần quản lý không phải là một giá trị, mà là mối quan hệ giữa chúng.
Một lần gửi yêu cầu, bốn tầng dữ liệu
Tầng trên cùng là các header HTTP. Ngoài User-Agent và Accept-Language, Chrome gửi kèm sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform, rồi cả nhóm sec-fetch-site, sec-fetch-mode, sec-fetch-dest. Đây là văn bản do network stack phát ra, không phải kết quả của một lệnh JavaScript trên trang.
Tầng thứ hai là những gì script trong trang đọc được: navigator.userAgent, navigator.userAgentData, navigator.languages, Intl.DateTimeFormat gọi không có tham số, Date, kích thước màn hình, số luồng, canvas. Một bản vá bằng injection thường chỉ chạm được vào tầng này, hoặc một phần của nó.
Tầng thứ ba là gói ClientHello trong bắt tay TLS: danh sách cipher, danh sách extension, supported_groups, key_share, signature_algorithms, ALPN, các giá trị GREASE xen vào. Tầng thứ tư gồm khung SETTINGS của HTTP/2, giá trị WINDOW_UPDATE, thứ tự bốn pseudo-header, rồi TTL, cửa sổ TCP và thứ tự các option trong gói SYN.
Bốn tầng ấy không chia sẻ một công tắc chung. Sửa navigator.userAgent bằng script không đổi được header cùng tên trong request, và dù có đổi được header, gói ClientHello vẫn cứ do thư viện mạng của bản Chromium đang chạy quyết định. Một cấu hình được dựng bằng cách sửa từng ô vì thế dễ thành một tập hợp lai: vài ô lấy từ cấu hình giả, vài ô lấy từ máy thật, vài ô lấy từ thư viện mạng của một bản dựng khác hẳn. Một trình duyệt chống phát hiện phải quản lý cả bốn tầng cùng lúc, chứ không chỉ tầng mà script đọc được.
User-Agent bị đóng băng, Client Hints lại do cơ chế khác sinh ra
Chromium công bố lộ trình cắt giảm User-Agent từ tháng 9 năm 2021, chia thành bảy giai đoạn. Chrome 101 đưa phần MINOR.BUILD.PATCH về 0.0.0. Chrome 107 bắt đầu áp dụng chuỗi UA rút gọn cho desktop cùng các API liên quan là navigator.userAgent, navigator.appVersion và navigator.platform. Chrome 110 làm điều tương tự với Android, Chrome 113 kết thúc thử nghiệm deprecation. Hệ quả dễ thấy nhất: cụm Windows NT 10.0 trong UA không còn phân biệt Windows 10 với Windows 11, và số hiệu Chrome trong chuỗi này chỉ còn mức major.
Cùng lúc, một nguồn thông tin khác lại được mở ra. Từ Chrome 89, ba hint entropy thấp là Sec-CH-UA, Sec-CH-UA-Mobile và Sec-CH-UA-Platform được gửi mặc định trên mọi request. Muốn có bản số đầy đủ, phiên bản nền tảng, kiến trúc, số bit hay model, server phải gửi Accept-CH trong response, hoặc trang phải gọi navigator.userAgentData.getHighEntropyValues. Tài liệu Chrome Developers ghi rõ Client Hints chỉ đi qua kết nối an toàn, một header Accept-CH mới thay hoàn toàn danh sách hint cũ thay vì bổ sung vào, và request sang origin khác cần Permissions-Policy cho từng hint. Bản thảo UA Client Hints của WICG, bản ngày 26 tháng 10 năm 2026, liệt kê mười một header Sec-CH-UA và đánh dấu User-Agent là deprecated.
Chỗ gây nhầm lẫn nằm ở DevTools Protocol. Trang định nghĩa Emulation.setUserAgentOverride viết rằng userAgentMetadata phải được thiết lập thì các header Client Hint mới được gửi, và các giá trị tùy chọn bị thiếu sẽ do chính target điền bằng thứ nó vẫn thường dùng. Nói cách khác, sửa mỗi chuỗi userAgent không làm Sec-CH-UA đổi theo. Nếu bạn điền brands nhưng quên platformVersion, giá trị nhận được có thể là của máy thật. Kết quả là một request mang số hiệu Chrome do bạn chọn, cạnh một phiên bản nền tảng do hệ điều hành thật cung cấp. Cơ chế này đọc được trực tiếp trong tài liệu chính thức, không cần suy đoán về cách một trang web phát hiện gian lận.
GREASE khiến hai giá trị bằng nhau trở thành một tiêu chí sai
Bản thảo WICG dành hẳn một mục cho danh sách brand kiểu GREASE. Theo đó, brands phải có nhiều hơn một phần tử, trong đó có một giá trị tùy ý, và thứ tự các phần tử phải thay đổi theo thời gian để phía nhận không bám vào vị trí cố định. Cùng một mục lại khuyến nghị giữ nguyên phần GREASE trong suốt vòng đời của một significant version, nhằm giảm nhiễu cho cache và phân tích. Văn bản còn nêu thẳng: với mục đích phân tích, nên coi cả danh sách brand như một đơn vị và so với các danh sách đã biết của từng cặp trình duyệt và phiên bản, thay vì tách từng phần tử ra đọc. Tài liệu Chrome Developers cũng cảnh báo giá trị có thể rỗng, không được trả về, hoặc thay đổi.
Vì vậy, phép kiểm hai chuỗi có giống hệt nhau không là phép kiểm yếu cho lớp này. Một ví dụ hay bị đọc nhầm: UA ghi Windows NT 10.0 trong khi Sec-CH-UA-Platform-Version trả về một số khác. Hai giá trị này không được sinh ra từ cùng một chỗ. Theo thuật toán getting the platform version trong bản thảo, Windows 10 trở lên lấy số nguyên từ Windows.Foundation.UniversalApiContract rồi chuẩn hóa thành chuỗi ba đoạn; bản thảo không đưa ra ánh xạ riêng cho Windows 11, cũng không gán một con số cụ thể cho hệ điều hành đó. Không tìm thấy quy định như vậy không có nghĩa là quy định tồn tại, và cũng không có nghĩa là cặp giá trị kia do giả mạo. Tiêu chí nên là: tổ hợp này có thể xuất phát từ một bản dựng thật hay không.
Sự khác biệt giữa bằng nhau và cùng thuộc một tổ hợp có thật là lý do nhiều bảng kiểm tra trên mạng cho kết quả xanh mà cấu hình vẫn trông kỹ. Chúng kiểm từng ô một.
TLS nằm dưới JavaScript, và JA4 đã bỏ qua thứ tự
Gói ClientHello được phát đi trước khi có bất kỳ script nào chạy, bởi bộ mã mạng của trình duyệt. Không có API JavaScript nào cho phép trang sửa danh sách cipher hay thứ tự extension của chính kết nối đang dùng. JA3, thuật toán do Salesforce đưa ra, lại lấy đúng thứ tự extension làm một trong năm trường đưa vào MD5.
Fastly ghi nhận ngày 8 tháng 2 năm 2023 rằng một cập nhật của Chrome phát hành ngày 20 tháng 1 đã bắt đầu hoán vị các extension trong ClientHello. Tính năng vốn dự kiến lên Chrome 110 nhưng xuất hiện từ Chrome 108 và 109. Số hoán vị xấp xỉ mười lăm giai thừa, tức khoảng mười mũ mười hai, nên mỗi kết nối mới gần như cho một JA3 khác nhau. RFC 8446 cho phép extension đứng ở bất kỳ thứ tự nào, ngoại trừ pre_shared_key phải đứng cuối. Mục đích của thay đổi là tránh để server cố định hóa thứ tự, không liên quan đến nhận diện.
JA4 của FoxIO được thiết kế quanh thực tế đó. Chuỗi có ba đoạn: đoạn đầu ghi giao thức, phiên bản TLS, có SNI hay không, số cipher, số extension và hai ký tự đầu cuối của giá trị ALPN đầu tiên; đoạn giữa là mười hai ký tự đầu của sha256 trên danh sách mã hex cipher đã sắp xếp; đoạn cuối là mười hai ký tự đầu của sha256 trên danh sách extension đã sắp xếp, bỏ SNI và ALPN, rồi nối thêm signature_algorithms theo đúng thứ tự xuất hiện. GREASE bị bỏ qua ở mọi nơi. Thứ tự bị loại khỏi phép đo, thứ còn lại là tập hợp.
Với lớp TLS, câu hỏi chuyển sang: tập hợp cipher, extension, ALPN và signature_algorithms này có thể đi cùng số hiệu Chrome mà User-Agent vừa khai báo hay không. Một request khai Chrome 151 nhưng mang bộ ClientHello mặc định của một thư viện HTTP khác sẽ lệch ngay ở đây, dù mọi giá trị JavaScript trên trang đều đã được sửa đúng. Trang TLS Client Test của BrowserLeaks cho xem JA4, JA3, danh sách cipher, danh sách extension, ECH và key_share trong cùng một màn hình; API echo của tls.peet.ws còn in thêm dấu vân tay HTTP/2 kiểu Akamai và đặc trưng TCP. Đây là cấu trúc quan sát được, ai cũng tự mở ra kiểm lại được. Không có tài liệu công khai nào cho biết bên nhận chấm điểm từng trường ra sao, nên phần này chỉ khẳng định các trường cùng hiện diện, chứ không khẳng định trọng số của chúng.
Múi giờ, ngôn ngữ và địa chỉ IP: ba giá trị không tự quyết
Intl.DateTimeFormat gọi không có tham số sẽ dùng cấu hình hiện tại của runtime. Phương thức resolvedOptions trả về locale, calendar, numberingSystem, timeZone và đôi khi hourCycle, trong đó timeZone là tên IANA như Asia/Ho_Chi_Minh hay Europe/Berlin, mặc định lấy từ múi giờ của runtime. Cùng lúc, độ lệch UTC mà Date trả về ứng với thời điểm hiện tại và đổi theo quy ước giờ mùa hè. Một cấu hình đặt timeZone là Europe/Berlin nhưng đến tháng mười một vẫn cho độ lệch của mùa hè đã tự mâu thuẫn bên trong một tầng, và sửa User-Agent hay proxy không sửa được nó.
Ngôn ngữ cũng có hai nơi để đọc. MDN ghi nhận navigator.languages là mảng các tag BCP 47 xếp theo mức ưu tiên, navigator.language chính là phần tử đầu, còn Accept-Language trong mỗi request thường liệt kê cùng các locale với giá trị q giảm dần. Chrome và Safari còn chèn thêm tag rút gọn chỉ còn phần ngôn ngữ, kiểu en-US,en;q=0.9. Safari luôn chỉ đưa một ngôn ngữ, Chrome làm vậy ở chế độ ẩn danh, theo mô tả của MDN về giảm entropy. Nghĩa là độ dài danh sách, hình dạng các tag rút gọn và dãy q đều là một phần của cùng một dấu hiệu, không chỉ là tag đứng đầu.
Rồi đến nhóm địa lý: địa chỉ IP nhìn từ server, nơi phân giải DNS, địa chỉ ứng viên WebRTC, múi giờ IANA, Accept-Language và đôi khi cả đơn vị tiền tệ hay định dạng số do Intl suy ra. Chúng không cần giống hệt nhau, chúng cần kể được một hoàn cảnh có thật. Asia/Ho_Chi_Minh quanh năm ở UTC+7 và không đổi giờ, Europe/Berlin thì có hai mốc trong năm. Một exit node ở Đức, múi giờ Việt Nam, Accept-Language tiếng Việt và DNS của nhà mạng nội địa có thể cùng đúng trong một số trường hợp, nhưng đó vẫn là một hoàn cảnh phải tự giải thích được, thay vì bốn ô được chỉnh riêng rẽ.
Việc server đối chiếu chéo các cặp này với nhau là suy luận vận hành, chưa thấy được ghi thành điều khoản trong tài liệu công khai của một nền tảng nào. Tài liệu công khai không nêu trọng số, và việc không nêu không có nghĩa là các tín hiệu đó bị bỏ qua.
Ba dạng lệch và thứ tự nên sửa
Nhóm thứ nhất là những tổ hợp không thể xảy ra ở một trình duyệt thật. Sec-CH-UA-Mobile mang ?0 trong khi UA là Android là một ví dụ; danh sách brands chỉ có đúng một phần tử cũng vậy, vì bản thảo WICG dùng MUST cho yêu cầu có nhiều hơn một. Độ lệch UTC không khớp quy ước giờ mùa hè của chính timeZone đang khai báo nằm ở nhóm này. Một lệch duy nhất ở nhóm này đủ để bỏ qua mọi nỗ lực ở nhóm khác, nên sửa trước, rồi mới nói đến tính nhất quán giữa các profile.
Nhóm thứ hai là những tổ hợp có thể xảy ra nhưng hiếm: mười profile đều có cùng thứ tự năm ngôn ngữ, cùng dãy q, cùng độ phân giải, cùng số luồng và cùng thời điểm đăng nhập trong ngày. Từng giá trị đều hợp lệ. Cái lộ ra là sự đồng dạng giữa các profile, và sửa bằng cách randomize từng ô một thường làm tình hình tệ hơn, vì xác suất tạo ra một tổ hợp rơi vào nhóm thứ nhất tăng lên theo số ô được tung độc lập.
Nhóm thứ ba cần một mốc ngoài mới kết luận được: JA4 của một bản Chrome cụ thể, danh sách brands ứng với một significant version, hay bộ SETTINGS của HTTP/2 theo từng bản dựng. Thiếu một bảng đối chiếu riêng thì không thể nói một mình chuỗi JA4 là đúng hay sai, chỉ có thể ghi nhận nó vào một ngày, kèm số hiệu trình duyệt, rồi so với lần sau. Cách phân ba nhóm và thứ tự ưu tiên ở đây là suy luận vận hành, không có hệ thống phòng thủ nào công bố thứ tự ấy.
Tự kiểm, không cần cài gì
Mở một trang echo TLS và một trang echo header. Ghi lại năm nhóm: brands cùng platformVersion, Accept-Language, timeZone kèm độ lệch UTC hiện tại, JA4, và dãy SETTINGS của HTTP/2. Ghi cả ngày hôm đó.
Đổi sang profile thứ hai, làm lại đúng năm nhóm ấy. Đừng hỏi từng dòng có đạt hay không; hỏi xem hai lượt ghi chép có dựng được hai cái máy thật, khác nhau, và mỗi cái tự khớp trong suốt một tuần hay không. GREASE, giờ mùa hè và số hiệu Chrome đều thay đổi theo thời gian, nên một lần kiểm không dùng được cho lần sau.
Khi cấu hình được giữ nguyên giữa các lần mở
Sau khi đối chiếu chéo năm lớp, thứ còn lại cần quản lý là khả năng lặp lại: cùng một tổ hợp giá trị xuất hiện ở mỗi lần mở, trên mỗi máy, và vẫn được ghi chép đủ để so với lần trước. Đây là chỗ dễ nhầm giữa đã cấu hình và đang nhất quán. Một bản cập nhật trình duyệt kéo theo lúc đó thay đổi số hiệu trong UA, danh sách brands, bộ cipher, extension và cả tham số HTTP/2 cùng lúc; một cấu hình đóng băng từ tháng trước vì thế hết hiệu lực mà không ai sửa tay vào ô nào. Vì vậy tính nhất quán có thời hạn, và thời hạn ấy do lịch phát hành của bản dựng quyết định.
Nhìn vào danh sách tham số trên trang chủ của BitBrowser sẽ thấy rõ điểm này: UserAgent, Language, Timezone, Proxy, DNS, WebRTC, IPv4, IPv6 và Geo được liệt kê thành từng mục riêng, nằm ở những tầng khác nhau đã nói ở trên. Một công cụ dạng đó giữ cho cả nhóm giá trị đi cùng một profile và lặp lại nguyên vẹn ở mỗi lần mở; nó không quyết định quan hệ giữa các giá trị ấy, và đây là ví dụ để đọc cấu trúc cấu hình, không nhằm giới thiệu sản phẩm. Các trình duyệt cùng nhóm, hay một bản Chromium tự biên dịch, cũng chia trường theo cách đại thể giống nhau. Với vài profile, một cái máy thật dùng lâu không cần công cụ nào: trước mỗi lần đăng nhập, mở lại hai trang echo kia và đặt năm nhóm giá trị vừa ghi cạnh tờ giấy của lần trước là đủ.
All rights reserved