WebAudio im lặng: Vì sao một trang web vẫn giữ tai nghe Bluetooth của bạn

Không có gì là hoàn toàn im lặng

Giả sử bạn có một chiếc tai nghe Bluetooth hỗ trợ multipoint.

Nó kết nối cùng lúc với:

  • điện thoại;
  • máy tính.

Bạn đang nghe nhạc trên điện thoại.

Máy tính nằm bên cạnh, im re.

Mọi thứ rất hòa bình.

Rồi bạn mở một trang AliExpress trên máy tính.

Không video.

Không quảng cáo phát nhạc.

Không tiếng ting.

Không có biểu tượng cái loa trên tab.

Bạn thậm chí bấm Mute tab.

Và rồi…

nhạc trên điện thoại tắt.

Chiếc tai nghe quay sang máy tính như một con chó vừa nghe ai đó mở túi bánh.

Máy tính rõ ràng không phát ra âm thanh nào, nhưng tai nghe lại hành xử như thể máy tính đang giữ đường audio.

Đóng tab AliExpress.

Nhạc trên điện thoại lập tức sống lại.

Nghe giống một lỗi Bluetooth vớ vẩn.

Nhưng đây chính là chỗ câu chuyện bắt đầu trở nên hơi điên.

Bởi vì trong máy tính, âm lượng bằng 0 không có nghĩa là không có âm thanh đang được xử lý.

Giống như một đường ống nước mà vòi cuối cùng đã khóa: bạn không thấy giọt nước nào chảy ra ngoài, nhưng máy bơm phía sau vẫn có thể đang gầm gừ, đường ống vẫn đầy áp lực, van vẫn mở và cả hệ thống vẫn tin rằng nó đang làm việc.

Một lập trình viên có nickname m-c-tech gặp đúng hiện tượng đó và bắt đầu đào xuống.

Càng đào, câu chuyện càng đi từ một chiếc tai nghe Bluetooth xuống JavaScript, WebAudio, CPU, FFT, browser fingerprinting và cuối cùng là câu hỏi khá khó chịu:

Một trang web thực sự biết bao nhiêu về cái máy đang nằm trên bàn của chúng ta? (Laserphile)

Ban đầu, nghi phạm là một cái video ngu ngốc nào đó

Phản ứng đầu tiên rất hợp lý:

Chắc AliExpress đang autoplay một cái video quảng cáo ở đâu đó.

Tác giả kiểm tra những thứ bình thường:

  • thẻ <audio>;
  • thẻ <video>;
  • HTMLMediaElement.play();
  • Media Session;
  • request tải media;
  • iframe chứa video hoặc audio.

Không có gì.

Trang vẫn im lặng.

Nhưng sau vài giây, tai nghe vẫn bị máy tính giành quyền ưu tiên.

Điểm đáng chú ý là hiện tượng không xảy ra ngay lập tức sau khi tải trang. Nó xuất hiện sau khi trang đã chạy một lúc.

Thế là thay vì nhìn vào <audio><video>, tác giả bắt đầu nhìn xuống một tầng thấp hơn:

Web Audio API.

Web Audio API là cái gì?

Nếu <audio> giống như một chiếc máy cassette:

1<audio src="hello.mp3">

thì Web Audio API giống như bạn mang nguyên một bàn mixer, máy synthesizer, equalizer và oscilloscope nhét vào JavaScript.

Bạn có thể tạo âm thanh từ con số.

Bạn có thể:

  • tạo sóng sine;
  • tạo sóng vuông;
  • tạo sóng sawtooth;
  • tăng giảm volume;
  • lọc tần số;
  • nén dynamic range;
  • trộn nhiều nguồn;
  • phân tích phổ tần số;
  • đo waveform;
  • đưa kết quả ra loa.

Trong WebAudio, âm thanh được tổ chức thành một audio graph.

Ví dụ đơn giản:

1Oscillator
2    |
3    v
4GainNode
5    |
6    v
7Speaker

OscillatorNode tạo ra một sóng tuần hoàn. GainNode thay đổi biên độ, còn AudioContext.destination thường đại diện cho thiết bị output cuối cùng như loa hoặc tai nghe. (MDN Web Docs)

Nghe thì có vẻ vô hại.

Và phần lớn thời gian nó đúng là vô hại.

Các game chạy trên web, synthesizer online, phần mềm chỉnh nhạc, voice changer hay visualizer âm thanh đều cần những API kiểu này.

Vấn đề nằm ở chỗ:

cùng một chiếc búa có thể dùng để đóng đinh, hoặc gõ thử xem bức tường nhà bạn làm bằng vật liệu gì.

Hai AudioContext bí mật

Tác giả sửa tạm AudioContext của trình duyệt để ghi lại mỗi khi trang tạo một context mới.

Ý tưởng đại khái như sau:

 1const OriginalAudioContext = window.AudioContext;
 2
 3window.AudioContext = class extends OriginalAudioContext {
 4    constructor(...args) {
 5        super(...args);
 6
 7        console.log("AudioContext created");
 8        console.trace();
 9    }
10};

Sau đó tiếp tục theo dõi những lời gọi:

1AudioNode.connect(...)

để xem audio graph được nối đi đâu.

Và đây là lúc con thỏ trắng chui khỏi cái mũ.

AliExpress tạo ra hai AudioContext đang chạy.

Cả hai cuối cùng đều nối tới:

1AudioContext.destination

Trong khi đó:

1<audio>           = 0
2<video>           = 0
3media play()      = 0
4MediaSession      = none
5âm thanh nghe thấy = 0

Stack trace dẫn tới hai JavaScript bundle thuộc nhóm AWSC:

1collina.js
2fireyejs.js

Các script bị obfuscate khá mạnh và có vẻ thuộc hệ thống browser security / anti-abuse của Alibaba. (Laserphile)

Âm thanh bằng 0 nhưng đường audio vẫn còn sống

Audio graph được phát hiện có hình dạng gần như thế này:

 1                  ┌──────────────┐
 2                  │ Sawtooth     │
 3                  │ Oscillator   │
 4                  └──────┬───────┘
 5 6 7                  ┌──────────────┐
 8                  │ AnalyserNode │
 9                  └──────┬───────┘
101112               ┌───────────────────┐
13               │ ScriptProcessorNode│
14               └────────┬──────────┘
151617                 ┌────────────┐
18                 │ GainNode   │
19                 │ gain = 0   │
20                 └─────┬──────┘
212223              AudioContext.destination
242526              Bluetooth / Speaker

Đây là chỗ rất dễ hiểu nhầm.

gain = 0 nghĩa là:

$$ output = input \times 0 $$

Cho dù input là:

$$ 0.31 $$

hay:

$$ -0.912 $$

thì cuối cùng:

$$ output = 0 $$

Tai người không nghe thấy gì.

Nhưng phía trước cái số 0 ấy, browser vẫn có thể đang:

  • sinh waveform;
  • chạy audio processing;
  • tính toán các sample;
  • chạy FFT;
  • đọc frequency bins;
  • giữ AudioContext;
  • duy trì audio backend.

MDN mô tả AudioContext.suspend() là thao tác có thể dừng tiến trình của audio context, tạm ngừng truy cập audio hardware và giảm CPU/battery usage. Điều đó cũng cho ta một cách nhìn ngược lại: một context vẫn chạy có thể tiếp tục duy trì công việc ở audio subsystem dù cuối graph chẳng có âm thanh nghe thấy. (MDN Web Docs)

Với trường hợp được báo cáo, trạng thái đó đủ để Windows/Firefox coi audio path là còn hoạt động.

Chiếc tai nghe Bluetooth không biết rằng:

“À, đây chỉ là một thuật toán fingerprinting đang nhân tín hiệu với zero.”

Nó chỉ nhìn thấy:

“Máy tính vẫn đang dùng audio.”

Thế là multipoint giữ kết nối với PC.

Điện thoại đứng ngoài cửa.

Nhạc chết.

Nhưng tại sao một website lại tự tạo sóng âm?

Đây mới là đoạn hay.

Giả sử tôi đưa cho 100 người một bài toán:

11234567 × 7654321

Tất cả đều đưa ra cùng một đáp án.

Không phân biệt được ai với ai.

Nhưng bây giờ tôi đưa một cỗ máy cực kỳ phức tạp:

1tạo waveform
2→ xử lý floating point
3→ filter
4→ transform
5→ FFT
6→ đọc một mảng số

Về lý thuyết, cùng input phải ra cùng output.

Trong thế giới vật lý…

không hẳn.

CPU khác nhau.

Instruction set khác nhau.

Thư viện DSP khác nhau.

Compiler khác nhau.

Browser implementation khác nhau.

Floating-point có những sai số cực nhỏ.

Một con chip có thể tính:

10.123456701

con khác:

10.123456698

Hai con số nhìn bằng mắt gần như giống nhau.

Nhưng máy tính không nhìn bằng mắt.

Nó có thể gom hàng trăm hay hàng nghìn giá trị đó lại rồi hash:

1audio result
2    |
3    v
4SHA-256
5    |
6    v
79a388c0dd04cfdc...

Thế là một tiếng nói không tồn tại đã trở thành một đặc điểm của thiết bị.

Đây chính là browser fingerprinting

Cookie giống như việc website dán lên áo bạn một tờ giấy:

1HELLO, USER #829173

Bạn xóa cookie.

Tờ giấy biến mất.

Fingerprinting chơi một trò khác.

Website nhìn bạn rồi hỏi:

 1Màn hình rộng bao nhiêu?
 2GPU gì?
 3WebGL render thế nào?
 4CPU có bao nhiêu core?
 5Browser hỗ trợ codec nào?
 6Canvas vẽ ra pixel gì?
 7Audio engine tính waveform ra sao?
 8Device pixel ratio bằng bao nhiêu?
 9WebRTC cư xử thế nào?
10Chuột di chuyển kiểu gì?

Mỗi câu riêng lẻ rất yếu.

Ví dụ:

18 CPU cores

Có hàng triệu máy như vậy.

Nhưng ghép lại:

 1Windows
 2Chrome
 31920 × 1080
 4DPR 1.25
 58 cores
 6WebGL result A
 7Canvas hash B
 8Audio hash C
 9Codec set D
10Timezone E
11...

thì đám đông bắt đầu nhỏ dần.

Giống trò chơi:

Ai mặc áo đỏ?

500 người giơ tay.

Ai mặc áo đỏ, cao 1m75?

80 người.

Ai mặc áo đỏ, cao 1m75, đi giày 42?

12 người.

Ai còn đang cầm một ổ bánh mì?

Một người từ từ nhìn xuống tay mình.

Xin chào.

AliExpress không chỉ nhìn vào audio

Theo phân tích client-side của tác giả, các bundle này còn kiểm tra hoặc thu thập nhiều loại signal khác như:

  • Canvas;
  • WebGL renderer;
  • WebGL extension;
  • shader precision;
  • kích thước screen và viewport;
  • device pixel ratio;
  • hardware concurrency;
  • device memory;
  • browser plugins;
  • codec audio/video;
  • hành vi WebRTC;
  • performance timing;
  • mouse;
  • touch;
  • focus;
  • scroll;
  • motion/orientation;
  • dấu hiệu thường xuất hiện ở browser automation.

Code cũng chứa logic serialize dữ liệu, mã hóa và gửi telemetry qua các cơ chế như fetch() hoặc sendBeacon(). (Laserphile)

Nhìn cả cụm thì đây rõ ràng là một hệ thống thu thập fingerprint-like signals khá rộng.

Nhưng phải cẩn thận ở đây.

Từ phía browser, chúng ta thấy:

1signal được đo
2→ dữ liệu được đóng gói
3→ dữ liệu được gửi đi

Nhưng không thể nhìn xuyên qua Internet để thấy database phía server.

Do đó chưa thể kết luận chắc chắn rằng:

1fingerprint này được dùng như một ID theo dõi vĩnh viễn

Nó cũng có thể chỉ là một feature trong hệ thống:

1fraud score
2bot score
3risk score
4account security
5abuse prevention

Tác giả bài gốc cũng nói rõ giới hạn này. (Laserphile)

Tại sao một trang thương mại điện tử lại muốn fingerprint người dùng?

Giả sử bạn vận hành một website có hàng triệu giao dịch.

Ngày đẹp trời xuất hiện một con bot:

1tạo 10.000 account
2→ lấy coupon khách hàng mới
3→ mua hàng
4→ xóa cookie
5→ tạo account mới

Nếu hệ thống chỉ hỏi:

1Có cookie chưa?

con bot sẽ cười.

1rm -rf cookies

Xong.

Nhưng nếu website còn thấy:

1GPU giống nhau
2Canvas giống nhau
3Audio giống nhau
4WebGL giống nhau
5Timing giống nhau
6Screen giống nhau
7Automation signals giống nhau

thì việc giả làm một thiết bị hoàn toàn mới khó hơn nhiều.

Một marketplace lớn phải đối mặt với:

  • scraping;
  • account takeover;
  • payment fraud;
  • coupon abuse;
  • fake account;
  • review manipulation;
  • automation;
  • bot mua hàng;
  • credential stuffing.

Vì thế việc tồn tại một hệ thống device risk / anti-fraud không có gì bất ngờ.

Điều đáng tranh luận không phải là:

“Tại sao họ chống bot?”

Mà là:

“Một trang mua sắm bình thường nên được phép chạm sâu tới đâu vào đặc tính thiết bị trước khi người dùng làm bất kỳ hành động nhạy cảm nào?”

Hai câu hỏi đó hoàn toàn khác nhau.

WebAudio fingerprint nghe đáng sợ, nhưng Firefox đã làm nó bớt đáng sợ khá nhiều

Sau khi bài gốc được thảo luận rộng rãi, Tom Ritter, một developer làm việc với Firefox, đào sâu hơn vào fingerprint cụ thể này.

Kết quả khá thú vị.

Firefox đã cố làm WebAudio output trở nên gần như ổn định từ Firefox 118.

Trong telemetry được Tom Ritter mô tả, 99,24% người dùng rơi vào chỉ ba giá trị fingerprint chính.

Ba nhóm này chủ yếu xuất phát từ khác biệt CPU:

1x86 / x64 không FMA
2x64 có FMA
3ARM / NEON

Nếu 10 triệu người cùng có một fingerprint thì fingerprint đó chẳng còn giống dấu vân tay nữa.

Nó giống việc cảnh sát hỏi:

Hung thủ có mấy chân?

“Dạ hai.”

Tuyệt vời.

Còn khoảng vài tỷ nghi phạm.

Mozilla sau đó sửa một phép fused multiply-add để gom hai bucket x86 lại với nhau trong Firefox 152. Tom Ritter kết luận WebAudio fingerprinting trên Firefox hiện đã gần như vô dụng khi đứng riêng lẻ, dù fingerprinting tổng hợp từ nhiều nguồn khác vẫn là một vấn đề. (Ritter) (Mozilla Bugzilla)

Đây là một chi tiết rất quan trọng.

AliExpress có code cố đo WebAudio không đồng nghĩa với việc WebAudio fingerprint đó đang phân biệt tốt người dùng Firefox.

Hai chuyện khác nhau.

Một người có thể đang cố soi dấu vân tay qua một tấm kính mờ.

Hành động soi vẫn xảy ra.

Nhưng thứ nhìn thấy có thể chẳng hữu ích bao nhiêu.

Và thế mới buồn cười: fingerprint có thể không hiệu quả, nhưng vẫn làm hỏng Bluetooth

Đây có lẽ là đoạn hài nhất của toàn bộ câu chuyện.

Một hệ thống cố phân biệt máy tính bằng WebAudio.

Firefox đã làm output gần như giống nhau giữa phần lớn người dùng.

Thế nhưng code vẫn chạy.

Audio graph vẫn hoạt động.

Audio path vẫn bị giữ.

Tai nghe vẫn không chuyển sang điện thoại.

Tức là:

1Fingerprint:
2gần như vô dụng
3
4Tác dụng phụ:
5rất hữu dụng trong việc làm người dùng tức

Mozilla Bugzilla đã có report từ khoảng hai năm trước về AliExpress: Windows báo “An audio stream is currently in use”, khiến máy không sleep dù người dùng không nghe thấy âm thanh nào. Các developer Firefox sau đó thấy OscillatorNodeAnalyserNode tiếp tục được xử lý. (Mozilla Bugzilla)

Có một bài học debugging khá đẹp ở đây:

Một bug hôm nay đôi khi chính là bằng chứng khảo cổ của một hành vi đã tồn tại nhiều năm.

Tại sao bấm Mute tab vẫn không cứu được?

Bởi vì trực giác của chúng ta về web audio thường là:

1website
2   |
3<video>/<audio>
4   |
5browser media playback
6   |
7speaker

Mute tab xử lý phần âm thanh người dùng nghe thấy trong thế giới đó.

Nhưng WebAudio là một graph xử lý tín hiệu:

1JavaScript
2   |
3AudioContext
4   |
5AudioNodes
6   |
7AudioDestination

Trang dùng WebAudio không nhất thiết phải có:

1<audio>

hay:

1<video>

nào cả.

MDN mô tả Web Audio API như một hệ thống node graph độc lập, nơi source, processing node và destination được nối trực tiếp với nhau. Quan trọng hơn, làm đầu ra im lặng không đồng nghĩa với việc gọi AudioContext.suspend(): thao tác suspend() mới tạm dừng tiến trình của context, ngừng truy cập audio hardware và giảm CPU/battery usage. (MDN Web Docs) (MDN Web Docs)

Vì vậy tab có thể hoàn toàn im lặng trong tai người dùng, trong khi graph xử lý và audio path vẫn còn sống. Ở trường hợp được báo cáo, nút mute không đóng context nên không giải quyết được tác dụng phụ lên Bluetooth.

Nói theo cách hơi ngáo một chút:

Bạn đã bịt miệng ca sĩ, nhưng phòng thu vẫn bật điện.

OfflineAudioContext còn thú vị hơn

Các hệ thống audio fingerprinting không nhất thiết phải dùng real-time AudioContext.

Chúng có thể dùng:

1OfflineAudioContext

Loại context này không phát âm thanh ra phần cứng. Theo MDN, nó xử lý graph nhanh nhất có thể rồi trả kết quả vào AudioBuffer: (MDN Web Docs)

1Oscillator
23Filter / Compressor
45OfflineAudioContext
67AudioBuffer
89hash()

Sau đó lấy kết quả:

18da3c911...

làm một signal cho fingerprint.

Cách này sạch hơn vì không cần giành audio device thật.

Vụ AliExpress đáng chú ý chính vì graph được quan sát lại nối tới real-time AudioContext.destination, từ đó sinh ra tác dụng phụ lên hệ thống Bluetooth. (Laserphile)

Một waveform có thể nhận diện CPU như thế nào?

Hãy tưởng tượng một chuỗi audio gồm các sample:

$$ x_1,x_2,x_3,\dots,x_n $$

Audio engine áp dụng một phép biến đổi:

$$ y_i = f(x_i) $$

Lý tưởng:

1CPU A → y
2CPU B → y

Nhưng floating-point trong đời thực có thể tạo ra:

1CPU A:
20.5213827421
3
4CPU B:
50.5213828017

Chênh lệch:

$$ 5.96 \times 10^{-8} $$

Một đứa trẻ nhìn vào sẽ nói:

Giống nhau mà.

SHA-256 nhìn vào sẽ nói:

Hai vũ trụ khác nhau.

Chỉ cần một byte khác:

1input A → hash A
2input B → hash B

Hai hash có thể khác hoàn toàn.

Đó là lý do browser vendors phải chủ động giảm entropy thay vì hy vọng sai số quá nhỏ sẽ không ai quan tâm.

Fingerprinting không cần một phép đo thần kỳ

Đây có lẽ là điều quan trọng nhất của câu chuyện.

Người ta thường hình dung fingerprinting như một hacker có một API bí mật:

1getUniqueComputerSerialNumber()

Không.

Web không hoạt động đẹp đẽ như vậy.

Nó giống một thám tử hơi rảnh.

Hắn không biết tên bạn.

Nhưng hắn thấy:

1một đôi giày size 42
2một cái áo màu xanh
3một vết xước trên đồng hồ
4một giọng nói hơi khàn
5một chiếc xe màu trắng
6thường xuất hiện lúc 8 giờ sáng

Không dữ kiện nào nói:

1ĐÂY LÀ ANH A.

Nhưng giao của chúng:

$$ F = A \cap B \cap C \cap D \cap E $$

có thể làm tập ứng viên nhỏ dần.

Browser fingerprinting cũng vậy.

Audio chỉ là một mảnh.

Canvas là một mảnh.

WebGL là một mảnh.

Screen size là một mảnh.

Timezone là một mảnh.

Hardware concurrency là một mảnh.

Một hạt cát chẳng nói được gì.

Nhưng đủ nhiều hạt cát có thể tạo thành khuôn bàn chân.

Còn Brave thì sao?

Brave đã dùng một chiến lược khá thú vị gọi là farbling đối với những API có thể dùng để fingerprint, trong đó có WebAudio.

Thay vì cố bắt mọi máy trả về một giá trị hoàn toàn giống nhau, Brave có thể thêm một lượng biến đổi nhỏ phụ thuộc site/session.

Ý tưởng là:

1fingerprint thật
23thêm noise có kiểm soát
45website A thấy X
6website B thấy Y
7session sau thấy Z

Tracker lúc này khó nối:

1X = Y = Z = cùng một người

Brave gọi cơ chế randomization này là farbling và áp dụng nó cho Canvas cùng WebAudio. (Brave)

Nhưng cần nói chính xác:

Các nguồn ở đây không chứng minh rằng Brave đã chặn riêng hai script AliExpress này.

Điều có thể nói chắc hơn là Brave có các cơ chế bảo vệ WebAudio fingerprinting nói chung.

Đừng biến:

1browser có anti-fingerprinting

thành:

1browser đã xác nhận chặn chính xác case AliExpress này

Hai mệnh đề trông gần giống nhau nhưng không phải một.

Vậy có phải AliExpress đang nghe lén microphone không?

Không có bằng chứng trong phân tích này cho thấy microphone được dùng để nghe người dùng.

Điều quan trọng là oscillator ở đây có thể tự tạo signal bên trong máy.

Không cần ai nói:

Alo alo một hai ba bốn.

Browser tự sinh:

1████╲____████╲____████╲____

rồi đo xem audio engine xử lý nó như thế nào.

Nó giống kiểm tra một cái chuông bằng cách tự gõ vào chuông.

Không cần nghe âm thanh trong phòng.

AnalyserNode của WebAudio có thể cung cấp dữ liệu time-domain và frequency-domain từ signal trong audio graph; MDN mô tả cơ chế này dựa trên việc phân tích các sample, bao gồm FFT cho miền tần số. (MDN Web Docs)

Vì vậy:

1audio fingerprinting

không đồng nghĩa với:

1microphone spying

Đây là hai chuyện rất khác nhau.

Có thể tự kiểm tra kiểu này không?

Có.

Một cách đơn giản là instrument AudioContext.

Ví dụ:

 1const OriginalAudioContext = window.AudioContext;
 2
 3window.AudioContext = class extends OriginalAudioContext {
 4    constructor(...args) {
 5        super(...args);
 6
 7        console.log(
 8            "New AudioContext",
 9            this.state,
10            new Error().stack
11        );
12    }
13};
14
15const originalConnect = AudioNode.prototype.connect;
16
17AudioNode.prototype.connect = function (...args) {
18    console.log(
19        this.constructor.name,
20        "→",
21        args[0]?.constructor?.name
22    );
23
24    return originalConnect.apply(this, args);
25};

Sau đó mở DevTools và xem graph nào được dựng lên.

Đây chỉ là đoạn demo để hiểu ý tưởng, không phải một browser security tool hoàn chỉnh.

Nhưng nó cho thấy một tư duy debugging khá hay:

Khi cái bạn nhìn thấy ở tầng trên không giải thích được hiện tượng…

hãy đi xuống một tầng.

 1HTML
 2 3JavaScript
 4 5Web API
 6 7Browser engine
 8 9Operating system
1011Driver
1213Hardware

Rất nhiều con ma trong máy tính biến mất khi ta chịu khó đi xuống cầu thang.

Nếu muốn chặn thì sao?

Tác giả bài gốc thử chặn hai họ script bằng uBlock Origin và quan sát thấy các AudioContext nói trên không còn được tạo trong bài test của mình. (Laserphile)

Nhưng cần nhớ chúng có vẻ liên quan đến anti-fraud / anti-abuse.

Chặn chúng có thể khiến website:

  • yêu cầu CAPTCHA nhiều hơn;
  • nghi ngờ browser;
  • gây lỗi login;
  • gây vấn đề khi checkout;
  • thay đổi hành vi trong tương lai.

Và script path hoàn toàn có thể được AliExpress thay đổi.

Đây không phải kiểu:

1thêm hai rule
2→ bảo vệ quyền riêng tư vĩnh viễn
3→ đi ngủ

Security chưa bao giờ dễ thương như vậy.

Điều thú vị nhất không phải AliExpress

Nếu chỉ đọc câu chuyện này như:

AliExpress có một đoạn JavaScript đáng ngờ.

thì hơi phí.

Điều thú vị hơn nhiều là ranh giới giữa thế giới ảo và thế giới vật lý.

Một đoạn JavaScript:

1gain.gain.value = 0;

trông vô hại.

Chỉ là một con số zero.

Nhưng con số zero ấy có thể tạo ra chuỗi:

 1JavaScript
 2 3WebAudio graph
 4 5audio processing thread
 6 7browser audio backend
 8 9Windows audio subsystem
1011Bluetooth audio path
1213tai nghe multipoint
1415điện thoại mất quyền phát nhạc

Một dòng code nằm trong RAM.

Nhưng cuối cùng nó thay đổi hành vi của một vật thể đang nằm trên đầu bạn.

Đó là điều mình thấy đẹp một cách hơi đáng sợ trong máy tính.

Không có gì là hoàn toàn “ảo”.

Một pixel trên màn hình cuối cùng vẫn là transistor.

Một request HTTP cuối cùng vẫn là electron chạy trong mạch điện, photon chạy trong sợi quang và sóng điện từ bay qua không khí.

Một mô hình AI cuối cùng vẫn là hàng tỷ phép nhân cộng làm transistor đóng mở rồi biến điện thành nhiệt.

Và một âm thanh có volume bằng zero…

vẫn có thể giữ cả một đường audio sống.

Một bug nhỏ đã làm lộ một hệ thống lớn

Nếu Bluetooth multipoint hoạt động hoàn hảo, có lẽ chẳng ai để ý.

Audio fingerprint chạy.

Telemetry được thu thập.

Tab vẫn im lặng.

Người dùng tiếp tục xem sản phẩm.

Mọi thứ chìm dưới mặt nước.

Nhưng một chiếc tai nghe từ chối chuyển sang điện thoại.

Một lỗi rất nhỏ.

Một sự khó chịu rất tầm thường.

Và chính cái khó chịu đó khiến ai đó mở DevTools.

Từ đó:

 1Bluetooth bug
 2 3AudioContext
 4 5hidden audio graph
 6 7obfuscated JavaScript
 8 9WebAudio fingerprint
1011browser fingerprinting system

Có một câu của Tom Ritter mình khá thích về tinh thần của vụ này, đại ý rằng khi một đoạn code tác động lên đủ nhiều nạn nhân, cuối cùng nó sẽ thay đổi một thứ gì đó khiến một người chú ý. (Ritter)

Máy tính hiện đại có hàng tỷ thứ diễn ra mà người dùng không bao giờ nhìn thấy.

Nhưng hệ thống vật lý có một tính xấu.

Nó hay để lại dấu chân.

CPU nóng lên.

Pin tụt.

Máy không sleep.

Tai nghe không chuyển.

Quạt quay.

Network nháy.

Một thứ gì đó lệch đi một chút.

Và đôi khi chỉ cần kéo một sợi chỉ rất nhỏ…

cả chiếc áo len bung ra.

Tổng kết

Vụ AliExpress này bắt đầu từ một hiện tượng rất đời thường: mở một website thì tai nghe Bluetooth không chịu phát nhạc từ điện thoại.

Đi xuống vài tầng, tác giả phát hiện hai WebAudio context được tạo bởi các script obfuscate, dựng audio graph có oscillator, analyser và cuối cùng nối qua GainNode có gain bằng zero tới audio destination. Người dùng không nghe thấy gì, nhưng audio subsystem vẫn có thể hoạt động và gây tác dụng phụ lên Bluetooth.

Những script này đồng thời đo nhiều signal thường xuất hiện trong browser/device fingerprinting như Canvas, WebGL, hardware, screen, codec, WebRTC và interaction data. Tuy nhiên, từ client-side không thể kết luận chính xác fingerprint được sử dụng hoặc lưu trữ thế nào ở server.

Một chi tiết khá thú vị là Firefox đã làm WebAudio fingerprinting kém hiệu quả đi rất nhiều, nên phép đo vẫn có thể chạy trong khi giá trị thu được chẳng còn phân biệt tốt người dùng. Trớ trêu thay, tác dụng phụ giữ audio path sống lại hoàn toàn có thật.

Và có lẽ đó mới là điều đáng nhớ.

Trong máy tính, “không nghe thấy gì” không có nghĩa là “không có gì đang xảy ra”.

Đôi khi phía sau một con số zero là oscillator đang chạy, FFT đang quay, CPU đang tính, driver đang giữ thiết bị, Bluetooth đang đổi trạng thái và một đoạn JavaScript bé tí đang kéo một sợi dây xuyên từ thế giới phần mềm xuống tận vật chất.

Xong cái audio bằng zero.

Quá dễ.

Cho đến khi tai nghe của bạn ngừng phát nhạc.

Tham khảo

  • laserphile — AliExpress webpage keeping multipoint Bluetooth headphones active with WebAudio fingerprinting. (Laserphile)
  • Tom Ritter — webaudio fingerprinting on alibaba. (Ritter)
  • Mozilla Bugzilla — Audio power request prevents sleep on Windows 11 on Aliexpress pages. (Mozilla Bugzilla)
  • Mozilla Bugzilla — thay đổi nhằm giảm entropy còn lại của WebAudio fingerprint. (Mozilla Bugzilla)
  • MDN — AudioNode, AudioContext, OfflineAudioContext, AnalyserNode và audio destination. (MDN Web Docs)
  • Brave — tài liệu về fingerprint randomization/farbling và các biện pháp bảo vệ fingerprinting. (Brave)

Bình luận