Tài liệu
/
Công thức
/

Thay thế template phía máy chủ

Thay thế template phía máy chủ

Pro
v6.10.0+

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 đó.

JavaScript

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áyMẫ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ụ:

JavaScript

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à ${...}.

JavaScript

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

JavaScript

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:

Văn bản

Trường Reserved Strings trong giao diện của trình làm rối với regex được nhập bằng dấu gạch chéo ngược đơn

API

JavaScript

Sau khi làm rối

JavaScript

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ự đó:

Mã

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

JavaScript

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:

Văn bản

Trường Reserved Strings trong giao diện của trình làm rối với regex được nhập bằng dấu gạch chéo ngược đơn

API

JavaScript

Sau khi làm rối

JavaScript

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:

Mã

Đầu ra thu được:

JavaScript

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 }:

JavaScript

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 ước UPPER_SNAKE cho 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 reservedStrings chạ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.