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

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

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

v6.10.0+

Chèn các giá trị được dựng từ máy chủ (ví dụ các biểu thức Go html/template `{{ .Field }}`) vào JavaScript đã làm rối VM mà không phá vỡ 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 API endpoint, một danh sách feature flag, một khối trạng thái khởi tạo, một build id, một nonce. Bạn làm rối JS bằng vmObfuscation: true, nhưng bạn cũng cần engine template thay thế các placeholder trong đầu ra đã làm rối sau khi làm rối kết thúc. Nếu các placeholder bị hấp thụ vào bytecode VM (mặc định cho chuỗi và định danh), engine template sẽ chẳng còn gì để thay thế.

Tôi nên chọn tùy chọn nào?

Giá trị máy chủ là…Dùng
Một biểu thức JS thô (mảng, đối tượng, số, boolean, lệnh gọi hàm)reservedNames + placeholder định danh
Một chuỗi, và bạn kiểm soát các dấu phân cách của templatereservedStrings + placeholder template-literal
Một chuỗi, và engine template buộc phải dùng những dấu phân cách nhất định (ví dụ Go {{ .Field }})reservedStrings + placeholder chuỗi hoặc template-literal

Mẫu 1 - reservedNames với một placeholder định danh

Phù hợp nhất cho: chèn các biểu thức JS thô (mảng, đối tượng, số, …) mà không phải bận tâm gì về việc thoát dấu ngoặc kép.

Hã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 đến nó trực tiếp, và liệt kê một regex khớp nó trong reservedNames. Dưới làm rối VM, định danh được định tuyến qua mảng reserved-expressions và xuất hiện nguyên văn trong đầu ra.

Mã nguồn

(function () {
    var initialState = __INITIAL_STATE__;
    bootstrap(initialState);
})();

Tùy chọn làm rối

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedNames: ['^__INITIAL_STATE__$']
});

Sau khi làm rối

Ở đâu đó trong đầu ra bạn sẽ thấy một mảng reserved-expressions tương tự như:

let _r = [__INITIAL_STATE__, /* …other entries… */];

Token __INITIAL_STATE__ sống sót qua việc đổi tên định danh và không bị hấp thụ vào bytecode VM.

Thay thế phía máy chủ (ví dụ Go)

tpl := template.Must(template.New("obf.js").Parse(obfuscated))
tpl.Execute(w, map[string]any{
    "InitialState": map[string]any{"user": "alice", "theme": "dark"},
})

…nơi template Go của bạn thay thế token bằng một biểu thức JS thô:

{{ `__INITIAL_STATE__` }} → {{ .InitialState | toJSON }}

Kết quả:

let _r = [{"user":"alice","theme":"dark"}, /* … */];

Không dấu ngoặc kép, không thoát ký tự - giá trị được chèn vào được engine JS phân tích như một biểu thức thông thường.

Mẫu 2 - reservedStrings với một placeholder template-literal

Phù hợp nhất cho: các engine template kiểu Go / Jinja yêu cầu những 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 một-quasi, không nội suy. Dưới làm rối VM, nó được định tuyến qua mảng reserved-expressions, giữ nguyên dạng backtick thô trong đầu ra.

Mã nguồn

(function () {
    var featureFlags = JSON.parse(`{{.Config.FeatureFlags}}`);
    applyFlags(featureFlags);
})();

Tùy chọn làm rối

UI

Để giữ lại placeholder {{.Config.FeatureFlags}} từ 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. UI lưu giá trị nguyên văn, nên khác với trong mã JS, các dấu gạch chéo ngược không được nhân đôi:
\{\{[^}]+\}\}

Trường Reserved Strings trong UI 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

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedStrings: ['\\{\\{[^}]+\\}\\}']
});

Sau khi làm rối

let _r = [`{{.Config.FeatureFlags}}`, /* … */];

Placeholder được giữ nguyên từng byte, bao gồm cả các dấu backtick bao quanh.

Thay thế phía máy chủ

Hãy thay {{.Config.FeatureFlags}} bằng một chuỗi JSON hợp lệ - nó sẽ nằm bên trong các dấu backtick khi chạy, vốn không đòi hỏi thoát dấu ngoặc kép bên trong:

flagsJSON, _ := json.Marshal(config.FeatureFlags) // e.g. {"newCheckout":true,"darkMode":false}
out := strings.ReplaceAll(obfuscated, "{{.Config.FeatureFlags}}", string(flagsJSON))

Khi chạy: JSON.parse(`{"newCheckout":true,"darkMode":false}`) - hoạt động.

Mẫu 3 - reservedStrings với một placeholder chuỗi có dấu nháy

Phù hợp nhất cho: mã nguồn buộc phải giữ ở ES5 (không có template literal), hoặc các trường hợp mà API xung quanh mong đợi một chuỗi ký tự thông thường.

Placeholder là một chuỗi ký tự bọc bằng dấu nháy đơn hoặc kép. Dưới làm rối VM, nó được định tuyến qua mảng reserved-strings, vốn được phát ra dưới dạng một mảng JS được serialize bằng JSON.stringify - luôn bọc bằng dấu nháy kép bất kể kiểu dấu nháy của đầu vào.

Mã nguồn

(function () {
    var tags = JSON.parse("{{.Page.Tags}}");
    renderTags(tags);
})();

Tùy chọn làm rối

UI

Để giữ lại placeholder {{.Page.Tags}} từ 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. UI lưu giá trị nguyên văn, nên khác với trong mã JS, các dấu gạch chéo ngược không được nhân đôi:

\{\{[^}]+\}\}

Trường Reserved Strings trong UI 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

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedStrings: ['\\{\\{[^}]+\\}\\}']
});

Sau khi làm rối

var _rs = ["{{.Page.Tags}}", /* … */];

Thay thế phía máy chủ

Vì placeholder nằm bên trong một chuỗi JS bọc bằng dấu nháy kép, payload JSON được chèn vào phải có các ký tự " bên trong được thoát bằng \":

raw, _ := json.Marshal(page.Tags) // e.g. ["news","tech","release"]
// Escape " for embedding inside a JS double-quoted string.
escaped := strings.ReplaceAll(string(raw), `"`, `\"`)
out := strings.ReplaceAll(obfuscated, "{{.Page.Tags}}", escaped)

Đầu ra thu được:

var _rs = ["[\"news\",\"tech\",\"release\"]", /* … */];

Khi 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 các dấu nháy không cân đối và ném một SyntaxError. Mẫu 2 tránh được điều này hoàn toàn bằng cách dùng dấu backtick.

Nhiều placeholder trong cùng một chương trình

Cả ba mẫu đều kết hợp được. Một regex reservedStrings duy nhất với một phép hoán vị có thể khớp mọi dạng placeholder mà engine template của bạn phát ra:

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    reservedNames: ['^__INITIAL_STATE__$'],
    reservedStrings: ['\\{\\{[^}]+\\}\\}']
});

Bạn có thể trộn các placeholder định danh (cho các giá trị JS thô) và các placeholder chuỗi (cho JSON được kết xuất) trong cùng một mã nguồn - hãy chọn theo từng placeholder dựa trên thứ mà máy chủ sẽ thực sự chèn vào.

Ghi chú về tương thích

  • Đừng tái sử dụng cùng một placeholder cho cả định danh lẫn chuỗi. Một reserved name như __TOKEN__ và một reserved string khớp __TOKEN__ mô tả hai đường mã khác nhau (mảng reserved-expressions so với mảng reserved-strings). Hãy dùng các dạng văn bản riêng biệt cho mỗi loại - ví dụ, một quy ước UPPER_SNAKE cho các placeholder định danh và một dạng được bọc bằng dấu phân cách ({{ ... }}, %{...}, <<<...>>>) cho các placeholder chuỗi. Nhờ đó một lỗi trong một regex không thể âm thầm khớp regex kia.
  • Các regex reservedStrings chạy trên các giá trị chuỗi thô. Regex được khớp với giá trị lúc chạy của chuỗi, không phải với văn bản mã nguồn. \{\{[^}]+\}\} khớp 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ể bị bọc trong nội dung phụ thêm ("prefix-{{.Field}}-suffix"), regex vẫn khớp nhưng toàn bộ chuỗi được giữ nguyên - hãy lên kế hoạch thay thế cho phù hợp.
  • Hoạt động với bất kỳ engine template nào. Mặc dù các ví dụ dùng cú pháp Go text/template, không có gì trong việc tích hợp javascript-obfuscator là đặc thù của Go. Bất cứ thứ gì có thể thực hiện thay thế ở mức chuỗi trên đầu ra của trình làm rối đều sẽ hoạt động: Rails ERB, Django, PHP short-tag, sed trong một pipeline CI, v.v. Hãy chọn các dấu phân cách mà engine của bạn phát ra một cách tự nhiên và không trùng với cú pháp JS thật.