Lấy khóa mảng bytecode từ backend
Giữ khóa giải mã bytecode của VM ở ngoài client: lấy nó từ backend của bạn khi trang được nạp bằng một hàm lấy khóa bất đồng bộ, được bảo vệ sau lớp xác thực để một bundle bị đánh cắp trở nên vô dụng.
Vấn đề
vmBytecodeArrayEncoding mã hóa bytecode của VM bằng một khóa. Nếu khóa đó được đóng gói
sẵn trong bundle, bất kỳ ai có tệp đều có đủ mọi thứ để giải mã nó. vmBytecodeArrayEncodingKeyGetter cho phép
khóa nằm ở nơi khác và được tạo ra khi chạy - nhưng một getter đồng bộ chỉ có thể đọc những gì đã sẵn có
trên client (một biến toàn cục, một cookie, localStorage). Để lấy khóa từ máy chủ của bạn - nhờ đó bạn có thể đặt nó sau lớp
xác thực và thu hồi được nó - getter phải là bất đồng bộ.
Giải pháp
Hãy bật vmAsyncExecutor (v7.3.0+). Với bộ thực thi bất đồng bộ, hàm lấy khóa có thể
trả về một Promise, nhờ đó nó có thể fetch khóa từ backend của bạn trước khi VM chạy. Ba tùy chọn phối hợp với nhau:
vmBytecodeArrayEncoding: true- mã hóa mảng bytecode.vmBytecodeArrayEncodingKey- khóa được dùng lúc biên dịch (giữ trên máy chủ của bạn, không nằm trong bundle).vmBytecodeArrayEncodingKeyGetter- một biểu thức JS trả về đúng khóa đó khi chạy. KhivmAsyncExecutorđược bật, nó có thể trả về mộtPromise; nếu không, getter phải trả về khóa một cách đồng bộ.
Cấu hình làm rối phía client
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmAsyncExecutor: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // e.g. 'mySecretKey123'
vmBytecodeArrayEncodingKeyGetter:
'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});
Biểu thức getter được nhúng nguyên văn và được đánh giá trong trình duyệt. Hãy giữ khóa lúc biên dịch
vmBytecodeArrayEncodingKey ở ngoài kho mã client của bạn - hãy truyền nó vào từ một biến môi trường hoặc secret lúc build,
và phục vụ đúng chuỗi đó từ endpoint bên dưới.
Hai khóa hoạt động 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ó đều đượ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 tạo ra, và mảng bytecode được mã hóa bằng khóa trộn thu được. - Khi chạy. Giá trị mà
vmBytecodeArrayEncodingKeyGettercủa bạn phân giải ra - do máy chủ của bạn trả về - được kết hợp với chính khóa nội bộ đó, vốn được dựng lại trên 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 phân giải ra đúng cùng một chuỗi mà bạn đã truyền
cho vmBytecodeArrayEncodingKey. Không mảnh nào là đủ khi đứng riêng: khóa của bạn mà không có khóa nội bộ thì không giải mã được
bytecode, còn khóa nội bộ thì vô dụng nếu thiếu khóa của bạn - đó là lý do việc chỉ phục vụ khóa cho những lời gọi đã xác thực
khiến một bundle bị đánh cắp trở nên vô dụng.
Phía máy chủ
Endpoint quyết định trả về khóa nào dựa trên bất cứ điều gì mà ứng dụng của bạn tin tưởng - một phiên hợp lệ, một
Origin hoặc Referer như mong đợi, một lần kiểm tra giấy phép, v.v. Điểm thú vị: thay vì từ chối những lời gọi không đáng tin, hãy trả về một
khóa sai. Bytecode khi đó sẽ giải mã ra rác và mã được bảo vệ tự nó hỏng, cách này kín đáo hơn một
lỗi 401 lộ liễu vốn nói cho kẻ tấn công biết chính xác cần vượt qua điều gì.
// Express example — the exact checks depend on your app
app.get('/api/vm-key', (req, res) => {
const origin = req.get('origin');
const trusted =
req.session?.user && // a valid session, and
origin === 'https://app.example.com'; // the expected production origin
res.type('text/plain').send(
// Real key for valid users; a decoy for everyone else
// (no session, or a localhost / unexpected origin).
trusted ? process.env.VM_KEY : process.env.VM_DECOY_KEY
);
});
