Thay thế template phía máy chủ
Chèn các giá trị do máy chủ kết xuất (ví dụ placeholder `{{ .Field }}` theo kiểu template của Go) vào JavaScript đã làm rối VM mà không làm hỏng quy trình làm rối.
Vấn đề
Backend của bạn (Go, Rails, Django, PHP, …) phục vụ một tệp JavaScript mà nội dung phải được kết xuất một phần theo
từng yêu cầu: một endpoint API, danh sách feature flag, một khối trạng thái ban đầu, một build id, một nonce. Bạn làm rối
JS với vmObfuscation: true, nhưng bạn cũng cần template engine thay thế các placeholder trong đầu ra đã làm rối
sau khi quá trình làm rối kết thúc. reservedNames và reservedStrings giữ placeholder hiển thị trong đầu ra để có
thể thay thế được. Nếu không có chúng, một placeholder dạng chuỗi bị hấp thụ vào bytecode VM, còn một placeholder dạng định
danh được sinh ra ở nhiều vị trí đã viết lại, nên template engine hoặc không tìm thấy gì để thay thế, hoặc làm hỏng đầu ra.
Cân nhắc nạp giá trị dưới dạng dữ liệu
Nếu có thể, hãy tránh chỉnh sửa đầu ra được bảo vệ: nạp cấu hình lúc chạy dưới dạng dữ liệu trước khi bundle được bảo
vệ chạy. Đặt nó trong một script riêng hoặc lấy nó từ một endpoint có xác thực. Cách này giữ nguyên đầu ra được bảo vệ,
nên vmSelfDefending có thể vẫn bật. Dù bằng cách nào, dữ liệu gửi tới trình duyệt vẫn hiển thị với trình duyệt đó.
Hãy dùng các mẫu dưới đây khi các giá trị thực sự phải được thay thế vào tệp đã làm rối.
Lưu ý về các mẫu Go: chúng thực hiện thay thế chuỗi thuần (strings.ReplaceAll / strings.NewReplacer) trên
đầu ra đã làm rối và tự xử lý việc escape, như được trình bày trong từng mẫu. Chúng không được chạy qua gói html/template
của Go - gói đó sẽ áp thêm cơ chế escape JavaScript theo ngữ cảnh của riêng nó, khiến payload bị escape hai lần. Nếu bạn
kết xuất qua html/template, hãy bỏ phần escape thủ công và để engine escape một lần.
Nên chọn tùy chọn nào?
| Giá trị từ máy chủ là… | Dùng |
|---|---|
| Một giá trị JS thô (mảng, đối tượng, số, boolean - bất kỳ giá trị JSON nào) | Mẫu 1 - reservedNames + placeholder dạng định danh |
| Một chuỗi (trường hợp phổ biến - JSON đã kết xuất, một build id, một nonce) | Mẫu 2 - reservedStrings + placeholder dạng template literal |
| Một chuỗi, khi mã phải giữ ở ES5 hoặc API bao quanh cần một string literal trong dấu nháy | Mẫu 3 - reservedStrings + placeholder dạng chuỗi trong dấu nháy |
Thay thế sau khi làm rối không tương thích với vmSelfDefending: true. Xem
Ghi chú về khả năng tương thích ở cuối công thức này.
Mẫu 1 - reservedNames với placeholder dạng định danh
Phù hợp nhất cho: chèn biểu thức JS thô (mảng, đối tượng, số, …) mà hoàn toàn không phải lo thoát dấu nháy.
Chọn một định danh đặc trưng sẽ không bao giờ trùng với mã thật, tham chiếu trực tiếp tới nó, và thêm một regex khớp với
nó vào reservedNames. Khi làm rối VM, định danh được chuyển qua mảng biểu thức dành riêng (reserved-expressions) và
xuất hiện nguyên văn trong đầu ra, ví dụ:
Thay định danh bằng một biểu thức hoàn chỉnh đã tuần tự hóa sang JSON. Đừng dán giá trị vào trong dấu nháy hay dấu
backtick: định danh không nằm trong một string literal, nên giá trị được chèn vào sẽ được JS engine phân tích như một
biểu thức thông thường. Hãy dùng một bộ tuần tự hóa đáng tin cậy, thoát < khi script được nhúng trong HTML, và kiểm thử
với các giá trị chứa dấu nháy, dấu gạch chéo ngược, ngắt dòng, dấu backtick và ${...}.
Mẫu 2 - reservedStrings với placeholder dạng template literal
Phù hợp nhất cho: các template engine kiểu Go / Jinja yêu cầu dấu phân cách như {{ .Field }}, vốn tình cờ là văn
bản JS hợp lệ khi được bọc trong dấu backtick.
Placeholder nằm bên trong một template literal chỉ có một quasi, không nội suy. Khi làm rối VM, nó được chuyển qua mảng biểu thức dành riêng, giữ nguyên dạng backtick thô trong đầu ra.
Mã nguồn
Tùy chọn của trình làm rối
Giao diện
Để giữ lại placeholder {{.Config.FeatureFlags}} trong mã nguồn ở trên, hãy thêm regex này vào trường Reserved Strings
với dấu gạch chéo ngược đơn. Giao diện lưu giá trị nguyên văn, nên khác với trong mã JS, dấu gạch chéo ngược không được
nhân đôi:

API
Sau khi làm rối
Placeholder được giữ nguyên từng byte, kể cả các dấu backtick bao quanh.
Thay thế phía máy chủ
Thay {{.Config.FeatureFlags}} bằng một chuỗi JSON đã được thoát cho template literal. Dấu backtick không yêu cầu thoát
" bên trong, nhưng một dấu gạch chéo ngược, một dấu backtick hoặc ${ trong dữ liệu vẫn sẽ làm thay đổi hoặc kết thúc
literal, vì vậy hãy thoát ba ký tự đó:
Lúc chạy: JSON.parse(`{"newCheckout":true,"darkMode":false}`) - hoạt động.
Vì sao nên ưu tiên template literal thay vì dấu nháy đơn/kép cho giá trị chuỗi? Dấu backtick không yêu cầu thoát "
bên trong dữ liệu JSON. Điều này quan trọng vì phần lớn giá trị do máy chủ kết xuất là JSON, và JSON đầy dấu nháy kép. Với
placeholder trong dấu nháy kép, bạn sẽ phải thoát mọi " bên trong (xem Mẫu 3); với dấu backtick, dữ liệu chỉ cần thoát
những dấu gạch chéo ngược, dấu backtick và ${ hiếm hoi.
Mẫu 3 - reservedStrings với placeholder dạng chuỗi trong dấu nháy
Phù hợp nhất cho: mã nguồn phải giữ ở ES5 (không có template literal), hoặc các trường hợp API xung quanh yêu cầu một string literal thông thường.
Placeholder là một string literal trong dấu nháy đơn hoặc kép. Khi làm rối VM, nó được chuyển qua mảng chuỗi dành riêng
(reserved-strings), được xuất ra dưới dạng một mảng JS tuần tự hóa bằng JSON.stringify: luôn dùng dấu nháy kép bất kể
kiểu dấu nháy của đầu vào.
Mã nguồn
Tùy chọn của trình làm rối
Giao diện
Để giữ lại placeholder {{.Page.Tags}} trong mã nguồn ở trên, hãy thêm regex này vào trường Reserved Strings với
dấu gạch chéo ngược đơn. Giao diện lưu giá trị nguyên văn, nên khác với trong mã JS, dấu gạch chéo ngược không được nhân đôi:

API
Sau khi làm rối
Thay thế phía máy chủ
Vì placeholder nằm trong một chuỗi JS dùng dấu nháy kép, dữ liệu JSON được chèn vào phải được thoát các dấu gạch chéo
ngược và các ký tự " bên trong:
Đầu ra thu được:
Lúc chạy: JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"].
Nếu bạn quên thoát ký tự, trình duyệt sẽ thấy dấu nháy không cân bằng và ném SyntaxError. Mẫu 2 tránh hoàn toàn dấu
nháy kép bằng cách dùng dấu backtick.
Nhiều placeholder trong một chương trình
Cả ba mẫu đều kết hợp được với nhau. Một regex reservedStrings duy nhất có phép chọn thay thế có thể khớp mọi dạng
placeholder mà template engine của bạn sinh ra - ở đây là cả kiểu {{ .Field }} lẫn %{ .Field }:
Bạn có thể kết hợp placeholder dạng định danh (cho giá trị JS thô) và placeholder dạng chuỗi (cho JSON đã kết xuất) trong cùng một mã nguồn: chọn cho từng placeholder dựa trên những gì máy chủ thực sự sẽ chèn vào.
Ghi chú về khả năng tương thích
vmSelfDefending làm hỏng việc thay thế sau khi làm rối
VM Self Defending phát hiện mọi thay đổi đối với đầu ra đã làm rối sau khi nó được dựng, kể cả một phép thay thế template hợp lệ, và khi đó mã được bảo vệ sẽ từ chối chạy.
Nếu bạn dựa vào thay thế template phía máy chủ, hãy đặt vmSelfDefending: false. Cũng hãy để selfDefending tắt: nó không
có tác dụng khi làm rối VM, nhưng khi không dùng VM, nó cũng cấm mọi thay đổi đối với đầu ra.
Mẹo về placeholder
- Đừng dùng lại placeholder giữa định danh và chuỗi. Một tên dành riêng như
__TOKEN__và một chuỗi dành riêng khớp với__TOKEN__mô tả hai nhánh mã khác nhau (mảng biểu thức dành riêng so với mảng chuỗi dành riêng). Hãy dùng dạng văn bản khác nhau cho mỗi loại, ví dụ quy ướcUPPER_SNAKEcho placeholder dạng định danh và dạng bọc trong dấu phân cách ({{ ... }},%{...},<<<...>>>) cho placeholder dạng chuỗi. Nhờ vậy, lỗi trong một regex không thể âm thầm khớp với loại kia. - Regex của
reservedStringschạy trên giá trị chuỗi thô. Regex được so khớp với giá trị lúc chạy của chuỗi, không phải văn bản mã nguồn.\{\{[^}]+\}\}khớp với các chuỗi chứa{{.something}}(hoặc bất kỳ dạng{{...}}nào khác). Nếu placeholder của bạn có thể được bọc trong nội dung khác ("prefix-{{.Field}}-suffix"), regex vẫn khớp nhưng toàn bộ chuỗi sẽ được giữ lại, vì vậy hãy lên kế hoạch thay thế cho phù hợp. - Hoạt động với mọi template engine. Dù các ví dụ dùng cú pháp Go, không có gì trong phần tích hợp javascript-obfuscator là dành riêng cho Go. Bất cứ thứ gì có thể thay thế ở mức chuỗi trên đầu ra của trình làm rối đều dùng được: Rails ERB, Django, PHP short-tags, sed trong pipeline CI, v.v. Hãy chọn dấu phân cách mà engine của bạn sinh ra một cách tự nhiên và không trùng với cú pháp JS thật.
