Header ETag sinh ra để tiết kiệm băng thông khi xác thực lại cache — nhưng một ETag riêng cho từng khách biến cache trình duyệt thành ID theo dõi.
Xóa cookie có cảm giác như nhấn nút reset, nhưng thực ra nó chỉ làm rỗng một cái hũ cụ thể. Header ETag được thiết kế cho một việc rất bình thường — báo cho trình duyệt biết "bạn đã có phiên bản mới nhất của file này rồi, khỏi tải lại" — và chính cơ chế đó có thể bị lợi dụng để gán cho mỗi khách một định danh duy nhất, được âm thầm xác nhận lại mỗi lần, sống trong cache của trình duyệt chứ không phải trong kho cookie. Một siêu cookie dựa trên cache dựng theo cách này sống sót qua một lần xóa cookie, đơn giản vì nó chưa bao giờ là cookie cả.
Điểm chính
- ETag vốn được thiết kế làm định danh phiên bản cho một file được cache, nhưng chẳng có gì ngăn máy chủ biến định danh đó thành duy nhất cho từng khách truy cập, thay vì cho từng phiên bản nội dung — biến việc xác thực lại cache thông thường thành một vòng trao đổi phục vụ theo dõi.
- Kỹ thuật này có thật, không phải lý thuyết suông: dự án evercookie của Samy Kamkar đã triển khai một cơ chế lưu trữ dựa trên ETag hoạt động thực sự từ năm 2010, và năm 2011 KISSmetrics bị phát hiện dùng ETag (cùng với Flash cookie) để "hồi sinh" các ID mà người dùng đã cố tình xóa, dẫn đến một vụ kiện tập thể.
- ETag không phải vector theo dõi qua cache duy nhất — cờ HSTS, cache favicon, và các trạng thái khác của trình duyệt mà một trang web có thể vừa đặt vừa đọc lại sau đó đều từng bị lợi dụng theo cách tương tự; ETag chỉ là trường hợp được ghi chép đầy đủ nhất.
- Các trình duyệt hiện đại đã bịt phần lớn lỗ hổng theo dõi liên trang. Kể từ Chrome 86, Chrome đã phân vùng HTTP cache theo trang web cấp cao nhất: một mục cache (và ETag của nó) được đặt trên một trang không còn đọc được từ trang khác — Safari và Firefox cũng đã triển khai các phiên bản phân vùng riêng của mình.
- Theo dõi ETag trong cùng một trang vẫn hoạt động, vì phân vùng chỉ chặn việc chia sẻ cache liên trang — một trang web đơn lẻ vẫn có thể gán cho trình duyệt của bạn một ETag duy nhất và đọc lại nó mỗi khi bạn quay lại chính trang đó, không cần cookie nào cả.
ETag thực sự dùng để làm gì
ETag là một header phản hồi HTTP mà máy chủ gắn vào một tài nguyên — hình ảnh, script, phản hồi API — làm định danh phiên bản. Mô tả của chính MDN về header này rất rõ ràng: nó "giúp cache hoạt động hiệu quả hơn và tiết kiệm băng thông, vì máy chủ web không cần gửi lại toàn bộ phản hồi nếu nội dung không thay đổi." Cách tạo ra giá trị này không được chuẩn hóa; trong thực tế nó thường là hash của nội dung, hash của mốc thời gian sửa đổi cuối, hoặc đơn giản là một số phiên bản.
Luồng xác thực lại rất đơn giản:
- Trình duyệt của bạn yêu cầu một tài nguyên. Máy chủ phản hồi với nội dung và một header
ETag: "abc123". - Trình duyệt cache cả nội dung lẫn giá trị ETag đó.
- Lần sau khi yêu cầu cùng tài nguyên, trình duyệt không tải lại một cách mù quáng mà gửi
If-None-Match: "abc123". - Nếu ETag hiện tại của máy chủ vẫn khớp, nó trả về một
304 Not Modifiedtrống — tốn ít băng thông nhất. Nếu nội dung đã thay đổi, nó gửi phiên bản mới cùng ETag mới.
Vòng trao đổi đó — trình duyệt chứng minh mình đã có một thứ gì đó, máy chủ xác nhận thứ đó còn mới hay không — về hình thức giống hệt một lần kiểm tra cookie. Điểm khác biệt duy nhất là định danh nằm trong header nào.
ETag trở thành định danh theo dõi như thế nào
Không có điều khoản nào trong đặc tả HTTP yêu cầu ETag phải đại diện cho phiên bản nội dung. Một máy chủ hoàn toàn có thể, ngay lần đầu bạn tải một pixel hoặc script theo dõi, tạo ra một ETag duy nhất gắn với từng khách, lưu giá trị đó ở phía máy chủ gắn với phiên của bạn, rồi trả về cho bạn dưới dạng ETag: "visitor-8f2c91a4". Trình duyệt của bạn không có cách nào phân biệt đây là một token báo cache còn mới hợp lệ hay một token theo dõi, nên nó ngoan ngoãn cache lại và gửi trả nguyên vẹn qua If-None-Match ở mọi yêu cầu tiếp theo cho tài nguyên đó — âm thầm nhận diện lại bạn ở mỗi lần tải trang, không cần cookie, không cần ghi vào localStorage, và hoàn toàn không cần JavaScript.
Đây chính xác là cơ chế mà dự án evercookie của Samy Kamkar đã trình diễn vào năm 2010: bên cạnh lưu trữ HTML5, Flash Local Shared Objects và nửa tá cơ chế khác, nó bao gồm một thành phần dựa trên ETag có hỗ trợ từ phía máy chủ — lưu một định danh vào HTTP ETag rồi đọc lại nó ở yêu cầu kế tiếp, với mục đích rõ ràng là để ID có thể được khôi phục ngay cả sau khi cookie bị xóa. Mục tiêu công bố của dự án là cho thấy một kẻ theo dõi kiên trì có thể ghép nối được bao nhiêu vector lưu trữ độc lập với cookie, và cache ETag hóa ra là một trong những vector bền bỉ nhất, vì xóa cookie chưa bao giờ làm rỗng HTTP cache của trình duyệt.
Kỹ thuật này nhanh chóng chuyển từ chứng minh khái niệm sang triển khai thực tế: năm 2011, các nhà nghiên cứu và nhà báo đã ghi nhận việc KISSmetrics — một nhà cung cấp phân tích được các trang như Hulu và Spotify sử dụng — dùng ETag cùng với Flash cookie để "hồi sinh" (respawning) các định danh theo dõi mà người dùng đã chủ động xóa. Phản ứng dữ dội về quyền riêng tư sau đó dẫn đến một vụ dàn xếp kiện tập thể, và đây vẫn là ví dụ thực tế rõ ràng nhất cho thấy theo dõi qua ETag từng được triển khai ở quy mô lớn chứ không chỉ là một minh họa khái niệm.
Theo dõi qua cache không chỉ có ETag
ETag nhận được nhiều sự chú ý nhất vì nó là ví dụ rõ ràng nhất, nhưng nó chỉ là một thành viên trong một họ lớn hơn gồm các siêu cookie dựa trên cache — những định danh được giấu vào bất kỳ trạng thái nào của trình duyệt mà một trang web có thể vừa đặt vừa đọc lại sau đó, nằm ngoài hũ cookie:
| Vector | Đặt cái gì | Đọc lại cái gì sau đó |
|---|---|---|
| Theo dõi qua ETag | Một ETag duy nhất trên tài nguyên được cache | If-None-Match ở yêu cầu tiếp theo |
| Siêu cookie HSTS | Những subdomain nào trong một tập hợp được "ghim" HSTS (mỗi bit mã hóa một phần của ID) | Trình duyệt có nâng cấp mỗi subdomain lên HTTPS mà không cần một round-trip mạng hay không |
| Cache favicon | Một favicon với URL duy nhất, được cache tách biệt khỏi các kho lưu trữ khác | Trình duyệt yêu cầu lại nó, hay phục vụ bản đã cache |
| Định thời Cache-Control | Một tài nguyên có tồn tại trong cache hay không | Chênh lệch thời gian tải giữa cache hit và một lần tải mới |
Cả bốn vector này đều dùng chung một mánh khóe cấu trúc với bounce tracking và giả mạo CNAME: chúng lợi dụng một cơ chế chưa bao giờ được thiết kế để phục vụ theo dõi, nên các biện pháp phòng thủ dành riêng cho cookie — chặn cookie bên thứ ba, xóa hũ cookie — hoàn toàn vô hiệu với chúng.
Vì sao nó sống sót qua xóa cookie và chế độ ẩn danh
Xóa cookie chỉ làm rỗng kho cookie. HTTP cache là một hệ thống con riêng biệt, có kho lưu trữ riêng và, ở hầu hết trình duyệt, có ô đánh dấu "xóa" riêng trong cài đặt quyền riêng tư — thứ mà người dùng thường bỏ qua, vì hộp thoại "xóa dữ liệu duyệt web" liệt kê cache và cookie như hai lựa chọn độc lập nhau. Một ETag được ghi vào cache sẽ nằm yên ở đó, không bị ảnh hưởng, cho đến khi bản thân cache bị xóa hoặc mục đó tự hết hạn.
Các cửa sổ riêng tư/ẩn danh cũng không giúp ích được gì nhiều: một phiên riêng tư vẫn dùng HTTP cache trong suốt thời gian cửa sổ mở, nên một ETag được đặt từ trước trong chính phiên riêng tư đó hoạt động y hệt như trong cửa sổ thường — nó chỉ bị loại bỏ khi cửa sổ đóng lại, cùng lúc với mọi thứ khác. Bài viết của chúng tôi về cách các trang web phát hiện chế độ ẩn danh bàn về một vấn đề liên quan nhưng khác — các trang web nhận ra bạn đang ở cửa sổ riêng tư qua những đặc điểm kỳ lạ của hạn mức lưu trữ — đó là câu hỏi hoàn toàn khác với việc liệu các định danh dựa trên cache có tồn tại được bên trong một phiên hay không.
Biện pháp của trình duyệt: phân vùng cache đã bịt lỗ hổng liên trang
Bản sửa lỗi có tính cấu trúc lớn nhất không nhắm cụ thể vào ETag — nó nhắm vào chính HTTP cache. Bắt đầu từ Chrome 86, Chrome đã phân vùng HTTP cache theo trang web cấp cao nhất: một tài nguyên được cache giờ không còn chỉ dùng URL làm khóa, mà dùng URL cộng với trang web cấp cao nhất đã yêu cầu nó (và trong các cải tiến sau này, cả trang web của frame). Bài viết của chính đội Chrome nêu rõ động cơ chính là để chống lại kiểu lạm dụng này — sự hiện diện của cache từng bị dùng để suy ra lịch sử duyệt web và để cài định danh theo dõi liên trang, và phân vùng loại bỏ cái cache dùng chung vốn cho phép cả hai điều đó xảy ra. Safari và Firefox từ đó cũng đã triển khai các cơ chế phân vùng cache tương đương của riêng mình.
Hiệu quả thực tế: một script theo dõi nhúng trên site-a.com không còn có thể đặt một ETag mà sẽ được đọc lại khi cùng script đó tải trên site-b.com, vì giờ mỗi trang web có phần cache riêng biệt, cách ly hoàn toàn của mình. Theo dõi ETag liên trang — phiên bản từng cho phép một nhà cung cấp quảng cáo tương quan bạn trên mọi trang web mà nó được nhúng vào — về cơ bản đã bị chặn ở các trình duyệt hiện tại.
Điều mà phân vùng không khắc phục là theo dõi trong cùng một trang: một trang web duy nhất mà bạn ghé thăm nhiều lần vẫn có thể gán cho trình duyệt của bạn một ETag duy nhất ngay lần tải đầu tiên và đọc lại nó mỗi khi bạn quay lại, hoàn toàn nằm trong phân vùng riêng của trang đó, không liên quan gì đến cookie. Phân vùng cache được xây dựng để chặn tương quan liên trang, chứ không phải để ngăn một trang web nhận ra khách quen của mình bằng phương tiện không phải cookie.
Cách phát hiện và giảm thiểu theo dõi qua ETag
Không có một công tắc duy nhất nào vô hiệu hóa vấn đề này theo cách "chặn cookie bên thứ ba" vô hiệu hóa việc theo dõi cổ điển — đó chính là lý do vì sao issue của chính Privacy Badger về phát hiện siêu cookie ETag (EFForg/privacybadger#2136) đến giờ vẫn còn mở: các máy chủ hợp pháp liên tục đặt ETag cho mục đích cache thông thường, nên việc phân biệt một định danh phiên bản với một định danh theo dõi từ bên ngoài là một bài toán heuristic thực sự khó, chứ không phải một phép so khớp mẫu đơn giản.
Một vài điều thực sự có ích:
- Xóa cả HTTP cache, không chỉ cookie. Hầu hết hộp thoại "xóa dữ liệu duyệt web" của trình duyệt liệt kê "Hình ảnh và file đã cache" là một mục riêng biệt với "Cookie và dữ liệu trang web khác" — hãy đánh dấu cả hai.
- Dùng chế độ riêng tư/ẩn danh cho những phiên bạn không muốn bị liên kết với nhau, vì cache của nó (giống như cookie) bị loại bỏ khi cửa sổ đóng lại — chỉ là nó không ngăn được việc theo dõi xảy ra bên trong chính phiên đó.
- Tắt hoàn toàn việc cache cho các bối cảnh nhạy cảm, thông qua tùy chọn "Disable cache" trong DevTools của trình duyệt, hoặc một tiện ích mở rộng bảo mật có thể loại bỏ hoặc ngẫu nhiên hóa cách xử lý
ETag/If-None-Match— đánh đổi bằng việc mất đi khoản tiết kiệm băng thông mà ETag vốn sinh ra để mang lại. - Yên tâm để phân vùng cache lo phần việc của nó cho trường hợp liên trang — nó đã được bật mặc định ở mọi trình duyệt chính hiện nay, nên bản sửa lỗi có giá trị cao nhất đã tự động có hiệu lực mà bạn không cần làm gì cả.
Không điều nào trong số này đòi hỏi phải đọc JavaScript trên trang, bởi vì trọng tâm của kỹ thuật này là nó chưa bao giờ chạm vào bất kỳ API lưu trữ nào mà một script có thể kiểm tra được — nó tồn tại hoàn toàn trong các header HTTP được trao đổi trước khi bất kỳ nội dung trang nào được tải.
Bản thân việc trao đổi ETag diễn ra ở tầng mà không script nào của trang đọc được, nhưng một định danh cache hiếm khi được dùng đơn lẻ — kẻ theo dõi ghép nó với mọi thứ khác mà trình duyệt của bạn để lộ. Công cụ kiểm tra dấu vân tay của BrowserInsight cho thấy các tín hiệu phía client như canvas, WebGL, bộ nhớ và những thứ khác mà một kẻ theo dõi sẽ kết hợp với một ID dựa trên cache — tất cả được đo ngay trong trình duyệt của bạn và không bao giờ gửi đi đâu để phân tích.
Câu hỏi thường gặp
Cửa sổ ẩn danh/riêng tư có ngăn được theo dõi qua ETag không?
Chỉ giữa các phiên, không phải bên trong một phiên. Cache của cửa sổ riêng tư bị loại bỏ khi bạn đóng nó lại, nên một ETag được đặt trong phiên đó không thể đọc lại được sau khi bạn mở lại trình duyệt — nhưng chừng nào cửa sổ riêng tư đó còn mở, việc nhận diện lại qua ETag hoạt động y hệt như trong cửa sổ thường.
Chặn cookie bên thứ ba có ngăn được siêu cookie ETag không?
Không, và đó chính là toàn bộ lý do khiến kỹ thuật này được chú ý. Việc chặn cookie bên thứ ba kiểm tra cụ thể header Cookie; nó không có khả năng nhìn thấy ETag hay If-None-Match, nên một định danh dựa trên cache trót lọt qua bộ chặn cookie mà không hề bị cản trở. Thứ thực sự ngăn được phiên bản liên trang là phân vùng HTTP cache — một cơ chế hoàn toàn riêng biệt của trình duyệt.
VPN hoặc chỉ xóa cookie có ngăn được việc này không?
Không, cả hai đều không. VPN thay đổi địa chỉ IP của bạn, không phải nội dung cache của trình duyệt. Xóa cookie làm rỗng kho cookie nhưng hoàn toàn không đụng đến HTTP cache — và bất kỳ ETag nào nằm trong đó — đó chính xác là kẽ hở mà kỹ thuật này được xây dựng để khai thác.
Theo dõi qua ETag ngày nay còn được dùng không?
Theo dõi ETag liên trang đã kém hiệu quả hơn nhiều kể từ khi Chrome, Safari và Firefox đều triển khai phân vùng HTTP cache, ngăn không cho một mục cache (và ETag của nó) được đặt trên một trang bị đọc từ trang khác. Theo dõi ETag trong cùng một trang — một trang web tự nhận diện lại khách quen của mình — vẫn khả thi về mặt kỹ thuật và hoàn toàn không bị phân vùng cache đụng tới.
Kết luận
Theo dõi qua ETag là lời nhắc rằng "xóa cookie đi" luôn chỉ là lời khuyên về một cơ chế lưu trữ cụ thể, chứ không phải một sự bảo đảm rằng bạn sẽ không còn bị nhận diện nữa. HTTP cache tồn tại để làm cho web nhanh hơn, và bất kỳ cơ chế nào mà máy chủ có thể vừa ghi vừa đọc lại được — một ETag, một cờ HSTS, một URL favicon — đều có thể bị biến thành một định danh ngay khi tính duy nhất được gắn vào nó thay vì một phiên bản nội dung thật sự. Phân vùng cache đã bịt được phần gây hại nhất — phiên bản liên trang — của vấn đề; trường hợp cùng trang là một nửa nhỏ hơn, âm thầm hơn, nhưng vẫn đáng để bạn để tâm vào lần tới khi xóa cookie mà có vẻ như chẳng có gì được reset cả.
Đọc thêm:

