Tách khóa Bytecode Array Encoding ra ngoài
Cung cấp khóa mã hóa bytecode VM của riêng bạn bằng vmBytecodeArrayEncodingKey và trả lại khóa đó lúc chạy thông qua một key getter - giữ ngoài bundle, đọc từ bộ lưu trữ phía client, hoặc lấy từ backend của bạn.
Xem
Obfuscator.io Async Executor: Async Bytecode Key Getter and Async-Only VM Virtualization
Obfuscator.io Custom Bytecode Key: Compile-Time Key and Runtime Key Getter (Cookie, Fetch, Decoy Key)
Các tùy chọn này làm gì
vmBytecodeArrayEncoding mã hóa mảng bytecode của VM để nó không nằm trong
đầu ra dưới dạng văn bản thuần. Theo mặc định, khóa mã hóa được suy ra từ môi trường và được dựng lại ở phía client, nên
bạn không bao giờ phải xử lý nó. Điều đó tiện lợi, nhưng dữ liệu khóa vẫn nằm trong bundle.
Hai tùy chọn cho phép bạn đưa khóa ra khỏi bundle và tự kiểm soát nó:
vmBytecodeArrayEncodingKey- khóa bạn cung cấp lúc biên dịch. Khi được đặt, nó được dùng thay cho khóa mặc định suy ra từ môi trường, và nó không được nhúng vào đầu ra đã làm rối.vmBytecodeArrayEncodingKeyGetter- một biểu thức JavaScript trả về chính khóa đó lúc chạy. Nó được nhúng nguyên văn và được đánh giá trong trình duyệt khi mã đã làm rối được tải.
Mục đích là tách biệt: vì khóa không nằm trong mã, một lần quét thuần tĩnh trên bundle không thể khôi phục được nó. Khóa vẫn phải có mặt lúc chạy để mã hoạt động, nên nó không thực sự bí mật, nhưng bạn quyết định nó đến từ đâu và ai được thấy nó.
Hai tùy chọn này đi thành cặp, và trình làm rối bắt buộc điều đó: đặt một tùy chọn mà thiếu tùy chọn kia là lỗi xác thực
lúc build (vmBytecodeArrayEncodingKey một mình không có cách nào lấy được khóa lúc chạy; một getter một mình không có
khóa lúc biên dịch nào để khớp). Chúng cũng không có tác dụng gì trừ khi mảng bytecode thực sự đang được mã hóa, nên
vmBytecodeArrayEncoding: true cũng phải được bật - hoặc vmSelfDefending: true, tùy chọn này buộc bật
vmBytecodeArrayEncoding ở bên trong. Hãy đặt cả hai khóa cùng với một trong hai tùy chọn đó.
Hai khóa kết hợp với nhau như thế nào
Khóa của bạn không bao giờ được dùng một mình: ở cả hai phía, nó được trộn với một khóa nội bộ do trình làm rối kiểm soát:
- Lúc biên dịch.
vmBytecodeArrayEncodingKeyđược kết hợp với một khóa nội bộ do trình làm rối suy ra, và mảng bytecode được mã hóa bằng khóa trộn thu được. - Lúc chạy. Giá trị mà
vmBytecodeArrayEncodingKeyGettercủa bạn trả về được kết hợp với cùng khóa nội bộ đó, vốn được dựng lại ở phía client từ nhiều yếu tố lúc chạy, để giải mã bytecode.
Vì cả hai phía đều trộn khóa của bạn với khóa nội bộ, getter phải trả về đúng chuỗi mà bạn đã truyền
vào vmBytecodeArrayEncodingKey. Không phần nào tự nó là đủ: khóa của bạn không có khóa nội bộ thì không giải mã được
bytecode, và khóa nội bộ vô dụng nếu thiếu khóa của bạn. Đó là lý do việc kiểm soát ai nhận được khóa của bạn mới là điều
thực sự bảo vệ mã.
Cung cấp khóa lúc chạy
Theo mặc định, getter là đồng bộ: biểu thức phải trả về khóa ngay lập tức khi mã đã làm rối
được tải. Hãy đọc nó từ bất kỳ nguồn nào đã có sẵn ở phía client: một cookie, localStorage, một biến toàn cục, hoặc
một phần tử DOM do máy chủ chèn vào.
Khóa phải tồn tại trước khi mã đã làm rối chạy:
Các nguồn đồng bộ khác hoạt động theo cùng cách; hãy chọn nguồn mà ứng dụng của bạn đã điền sẵn:
Đừng để khóa trong cùng tệp hoặc cùng script với mã đã làm rối. Nhúng nó vào đó sẽ phá hỏng toàn bộ mục đích: một
lần quét tĩnh trên bundle sẽ khôi phục được cả mã lẫn khóa của nó. Hãy lưu khóa ở một nguồn riêng, và đưa
vmBytecodeArrayEncodingKey lúc biên dịch vào từ một biến môi trường hoặc secret thay vì commit nó.
Lấy khóa từ backend của bạn (bất đồng bộ)
Yêu cầu vmAsyncExecutor · v7.3.0+Một getter đồng bộ chỉ có thể đọc những gì đã có sẵn ở phía client. Để lấy khóa từ máy chủ của bạn, nhằm
đặt nó sau lớp xác thực và có thể thu hồi, getter phải là bất đồng bộ, và điều đó yêu cầu
vmAsyncExecutor. Khi bật Async Executor, getter có thể trả về một
Promise, và VM sẽ chờ nó trước khi chạy.
Bật vmAsyncExecutor cũng thu hẹp phạm vi được ảo hóa: ở chế độ này chỉ các hàm async ngoài cùng được
biên dịch vào VM, còn mã đồng bộ không được ảo hóa (phần còn lại của quá trình làm rối vẫn được áp dụng). Nếu chương trình của bạn chủ yếu là đồng bộ, hãy bọc phần mã cần
bảo vệ trong một hàm async để nó vẫn được bao phủ - xem vmAsyncExecutor
để biết quy tắc đầy đủ.
Một getter trả về Promise bắt buộc phải có vmAsyncExecutor. Điều này không thể kiểm tra lúc build, nên một getter Promise
khi vmAsyncExecutor đang tắt sẽ thất bại lúc chạy.
Trên máy chủ, hãy quyết định trả về khóa nào dựa trên bất cứ thứ gì ứng dụng của bạn tin cậy: một phiên đã được xác thực, một
lần kiểm tra giấy phép, v.v. Chỉ riêng Origin hoặc Referer không xác thực được bên gọi, và các yêu cầu GET cùng nguồn
có thể bỏ qua Origin. Điểm mấu chốt: thay vì từ chối bên gọi không đáng tin, hãy trả về một khóa sai (VM_DECOY_KEY bên dưới).
Khi đó mã được bảo vệ tự thất bại (lỗi, kết quả sai, hoặc trang ngừng phản hồi), cách này kín đáo hơn nhiều so với một mã
401 lộ liễu cho kẻ tấn công biết chính xác cần vượt qua điều gì.
Hãy tắt bộ nhớ đệm phản hồi, để khóa của một bên gọi không bao giờ được phục vụ cho bên khác. Gắn khóa với đúng phiên bản dựng và triển khai khóa cùng với bundle. Một client nhận được khóa thật vẫn có thể kiểm tra nó lúc chạy.
Hãy phục vụ từ endpoint này đúng chuỗi mà bạn đã truyền vào vmBytecodeArrayEncodingKey lúc build. Getter
ở trên lấy một URL tương đối (/api/vm-key), nên một bản sao của bundle được lưu trữ trên origin khác sẽ yêu cầu /api/vm-key
từ chính origin đó, vì vậy nó không bao giờ nhận được khóa của bạn; khóa mồi nhử là thứ một bên gọi nhận được khi nó
có tới được endpoint của bạn nhưng không có phiên đáng tin cậy (một yêu cầu chưa xác thực, một bundle bị đánh cắp được
chuyển tiếp qua origin của bạn). Dù theo cách nào, khóa thật cũng không bao giờ tới nơi và mã được bảo vệ không chạy.
Thế nào là "hợp lệ" hoàn toàn phụ thuộc vào ứng dụng: một phiên đã xác thực, một giấy phép đã ký, hoặc bất kỳ
sự kết hợp nào. Dù thứ gì tạo ra khóa, hãy bọc nó trong một Promise và Async Executor sẽ chờ nó trước khi chạy
VM.
Khi khóa không khớp
Mã đã làm rối chỉ hoạt động khi getter trả về chính xác cùng khóa đã dùng trong lúc làm rối. Nếu các khóa
khác nhau, hoặc getter trả về undefined, null hay một chuỗi rỗng, mã sẽ thất bại lúc chạy: nó cho ra kết quả
sai, ném một lỗi lúc chạy thông thường, hoặc ngừng phản hồi.
Cố ý không có thông báo lỗi riêng dành cho khóa: một khóa sai không thể phân biệt được với bất kỳ lỗi lúc chạy nào khác. Vì vậy, khi một bundle được bảo vệ bằng VM chỉ ném lỗi, trả về kết quả sai hoặc bị treo kể từ khi tùy chọn này được dùng, hãy kiểm tra đường đi của khóa trước tiên: getter có trả về giá trị trên trang không, có trả về một chuỗi không rỗng không, và có trả về đúng giá trị bạn đã dùng để build không.
