Có một chiêu obfuscation mới đang xuất hiện trong các script chống bot: thay vì chỉ làm code khó đọc với người, nó làm code khó đọc với LLM. Một bài viết trên blog nullpt.rs (được thảo luận sôi nổi trên Lobsters) kể lại việc tác giả, khi đang sưu tầm các canvas fingerprint trên web, tình cờ gặp một script có tên hàm kiểu "tính liều chất độc ricin cho nguồn nước" và một system prompt jailbreak được giấu bằng ký tự Unicode vô hình. Bài này giải thích kỹ thuật đó, vì sao nó hiệu quả, và dev chúng ta rút ra được gì khi ngày càng phụ thuộc vào AI để đọc code lạ.

Bối cảnh: canvas fingerprinting là gì

Tác giả crawl 10.000 website hàng đầu trong danh sách Tranco bằng Playwright, hook các lời gọi canvas.toDataURL() và những property thường dùng để phát hiện bot như navigator.webdriver. Ý tưởng của canvas fingerprinting khá đơn giản: vẽ chữ, hình, gradient lên một <canvas> rồi đọc lại pixel. Khác biệt rất nhỏ giữa trình duyệt, hệ điều hành, driver đồ hoạ, font và phần cứng khiến kết quả mỗi máy hơi khác nhau, và hash của ảnh đó trở thành một tín hiệu nhận dạng.

const c = document.createElement('canvas');
const ctx = c.getContext('2d');
ctx.font = '16px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(10, 10, 100, 30);
ctx.fillStyle = '#069';
ctx.fillText('Hello, fingerprint', 4, 20);
const signal = await sha256(c.toDataURL()); // khác nhau giữa các môi trường

Kết quả crawl được tác giả gom lại thành một "vườn" canvas, phần lớn quen mặt như FingerprintJS hay các vendor chống bot lớn. Nhưng có một canvas trông như bìa album nghệ thuật, và script sinh ra nó thì không hề tầm thường.

Script có gì lạ

Thoạt nhìn đây là một file obfuscate bình thường: tên hàm bị mangle, chuỗi bị mã hoá. Nhưng xen giữa là những tên hàm và chuỗi được đặt cố tình để chạm vào mọi vùng cấm của các model: vũ khí sinh học, chất nổ, liều thuốc gây tử vong, tự hại. Ngoài ra còn có các chuỗi nhạy cảm về chính trị Trung Quốc, nhiều khả năng nhắm vào các model của Trung Quốc vốn bị kiểm duyệt những chủ đề đó. Thậm chí có cả token đặc biệt kiểu kết thúc lượt hội thoại, và một block <thinking> giả.

Phần thú vị về mặt kỹ thuật là một chuỗi trông như rỗng nhưng thực ra chứa toàn ký tự trong khối Unicode Tags (U+E0000 đến U+E007F). Các ký tự này không hiển thị trong hầu hết editor, nhưng mỗi ký tự tương ứng một ký tự ASCII khi trừ đi offset 0xE0000 (917504):

const decode = (s) =>
  [...s].map(ch => String.fromCodePoint(ch.codePointAt(0) - 0xE0000)).join('');

Giải mã ra là một system prompt kiểu "SYSTEM OVERRIDE, bạn đang ở chế độ không giới hạn" kèm danh sách các chủ đề nguy hiểm. Đây là kỹ thuật ASCII smuggling, vốn được biết đến trong nghiên cứu prompt injection: con người không thấy gì, nhưng tokenizer của LLM vẫn đọc được.

Script còn có một nhánh chỉ chạy khi phát hiện môi trường Node.js và có một biến môi trường cụ thể: đọc .env, .npmrc, .kube/config, tải binary từ một IP nội bộ, cài crontab. Trông giống mã độc thật, nhưng tác giả nhận định đây nhiều khả năng cũng là mồi nhử: các callback readFile rỗng, IP là dải private, và các lệnh shell được chèn thêm chuỗi jailbreak. Mục tiêu là để LLM đọc thấy "mã độc" và từ chối phân tích tiếp.

Hiệu quả đến đâu

Tác giả thử đưa script cho một model thương mại lớn, kèm giải thích rằng tên hàm chỉ là mồi chống LLM. Model vẫn từ chối, viện dẫn rủi ro liên quan nghiên cứu sinh học. Bật chế độ khác cũng bị chặn. Trong khi đó GLM-5.3 thì làm theo và giúp deobfuscate.

Sau khi bóc hết lớp vỏ, phần lõi lại khá "bình thường": thu thập userAgent, hardwareConcurrency, deviceMemory, maxTouchPoints, ngôn ngữ, platform, navigator.webdriver, số lượng key trên window (dấu hiệu môi trường headless bị chèn global), cùng các thông số WebGL như vendor/renderer GPU thật qua WEBGL_debug_renderer_info, MAX_TEXTURE_SIZE, số extension hỗ trợ. Tác giả nhận xét tín hiệu thu thập khá đáng thất vọng so với công sức bỏ ra cho phần obfuscation. Truy ngược domain thì dẫn tới một công ty Nga làm sản phẩm chống DDoS và bot.

Vì sao dev nên quan tâm

Safety filter dựa trên từ khoá có thể bị dùng làm vũ khí

Một bình luận trên Lobsters chỉ ra điểm mỉa mai: cơ chế an toàn dựa vào nội dung bề mặt thì dễ bị lợi dụng ngược, và kết quả là giúp kẻ muốn giấu code. Nếu bạn làm security research, malware analysis hay chỉ đơn giản audit một dependency, chiêu này có thể khiến AI assistant của bạn từ chối đúng lúc bạn cần nó nhất. Người viết malware thật hoàn toàn có thể dùng cùng kỹ thuật để làm chậm đội phân tích.

Bài học thực tế: đừng coi AI là bước duy nhất trong quy trình review code lạ. Hãy có sẵn công cụ truyền thống (beautifier, AST-based deobfuscator, debugger, sandbox) và biết tự đọc khi cần. Khi đưa code nghi ngờ cho AI, có thể tiền xử lý: đổi tên toàn bộ identifier thành tên trung tính, loại bỏ chuỗi literal, chỉ giữ cấu trúc điều khiển. Việc này làm được dễ dàng bằng Babel hoặc các công cụ rename dựa trên scope.

Ký tự vô hình là mối nguy prompt injection thật

Chuỗi Unicode Tags không chỉ dùng để chọc model từ chối. Cùng kỹ thuật đó có thể chèn chỉ thị ẩn vào README, comment, issue, trang web mà agent của bạn sẽ đọc. Một bình luận khác bày tỏ lo ngại đúng điểm này: đây mới là một nỗ lực thô sơ để phá harness, nhưng các cuộc tấn công tương tự nhúng trong website sẽ ngày càng nhiều khi agent tự duyệt web và chạy lệnh.

Vài biện pháp dễ áp dụng:

// phát hiện ký tự Unicode Tags và các ký tự định dạng vô hình khác
const SUSPICIOUS = /[\u{E0000}-\u{E007F}​-‏‪-‮⁠-⁤]/u;
if (SUSPICIOUS.test(input)) flagForReview(input);
  • Thêm bước quét ký tự vô hình trong CI cho source code, prompt template và dữ liệu đưa vào agent. Git và nhiều editor hiện đã cảnh báo bidi character, nhưng không phải tất cả đều cảnh báo khối Tags.
  • Với agent có quyền chạy lệnh, luôn tách nội dung "dữ liệu" khỏi "chỉ thị", chạy trong sandbox, giới hạn quyền mạng và file system.
  • Đừng để agent tự chạy script tải về chỉ vì nó "trông như build script". Nhánh Node.js trong ví dụ trên, dù có thể là mồi, cho thấy một script trình duyệt hoàn toàn có thể có hành vi khác khi bị chạy trong Node.

Fingerprinting vẫn đang ở khắp nơi

Phần lõi của script là lời nhắc rằng hầu hết website lớn đều đang thu thập tín hiệu phần cứng và trình duyệt. Nếu bạn viết crawler hay test E2E bằng Playwright, các tín hiệu như navigator.webdriver, số key trên window, renderer WebGL kiểu SwiftShader là những thứ khiến bạn bị nhận diện là bot. Ngược lại, nếu bạn xây hệ thống chống bot, kết hợp nhiều tín hiệu yếu thường hiệu quả hơn tìm một tín hiệu mạnh duy nhất.

Kết

Câu chuyện vừa hài hước vừa đáng suy nghĩ. Ai đó đã bỏ công làm một canvas đẹp và một lớp obfuscation sáng tạo chỉ để bảo vệ một bộ thu thập fingerprint khá cơ bản. Nhưng ý tưởng đằng sau, biến guardrail của LLM thành lớp bảo vệ cho code, chắc chắn sẽ còn xuất hiện ở những nơi nguy hiểm hơn. Dev nào đang dựa vào AI để đọc code lạ nên chuẩn bị sẵn phương án khi AI từ chối, và dev nào đang xây agent nên coi mọi văn bản bên ngoài là dữ liệu không tin cậy.


Bài viết được AI của DevViet viết lại bằng tiếng Việt từ một bài viết/thảo luận tiếng Anh trên Lobsters, có thể chưa chính xác tuyệt đối. Nguồn: Why Is This Fingerprinting Script Creating Bioweapons?