Pobieranie klucza tablicy kodu bajtowego z backendu
Trzymaj klucz deszyfrujący kod bajtowy VM poza klientem - pobieraj go z backendu podczas ładowania za pomocą asynchronicznego gettera, zabezpieczonego uwierzytelnianiem, dzięki czemu skradziona paczka pozostaje bezużyteczna.
Problem
Opcja vmBytecodeArrayEncoding szyfruje kod bajtowy VM przy użyciu klucza. Jeśli klucz ten trafi
do paczki, każdy, kto ma plik, dysponuje wszystkim, co potrzebne do jego odszyfrowania. Opcja vmBytecodeArrayEncodingKeyGetter pozwala
przechowywać klucz gdzie indziej i wytwarzać go w czasie wykonania — ale getter synchroniczny może odczytać wyłącznie to, co już znajduje się
po stronie klienta (zmienną globalną, ciasteczko, localStorage). Aby pobrać klucz z serwera — i dzięki temu zabezpieczyć go
uwierzytelnianiem oraz móc unieważnić — getter musi być asynchroniczny.
Rozwiązanie
Należy włączyć vmAsyncExecutor (v7.3.0+). Przy asynchronicznym executorze getter klucza może
zwracać obiekt Promise, dzięki czemu może pobrać (fetch) klucz z backendu przed uruchomieniem VM. Współdziałają tu trzy opcje:
vmBytecodeArrayEncoding: true— szyfruje tablicę kodu bajtowego.vmBytecodeArrayEncodingKey— klucz używany na etapie kompilacji (przechowywany na serwerze, nie w paczce).vmBytecodeArrayEncodingKeyGetter— wyrażenie JS zwracające ten sam klucz w czasie wykonania. Przy włączonymvmAsyncExecutormoże zwracać obiektPromise; bez niego getter musi zwrócić klucz synchronicznie.
Konfiguracja obfuskacji po stronie klienta
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())'
});
Wyrażenie gettera jest osadzane dosłownie i wykonywane w przeglądarce. Klucz vmBytecodeArrayEncodingKey używany na etapie
kompilacji nie powinien trafić do repozytorium kodu klienta — należy wstrzykiwać go ze zmiennej środowiskowej lub z sekretu podczas budowania
i serwować dokładnie ten sam ciąg z opisanego niżej endpointu.
Jak działają oba klucze
Klucz użytkownika nigdy nie jest używany samodzielnie — po obu stronach zostaje zmieszany z wewnętrznym kluczem kontrolowanym przez obfuskator:
- Etap kompilacji.
vmBytecodeArrayEncodingKeyjest łączony z wewnętrznym kluczem wyprowadzanym przez obfuskator, a tablica kodu bajtowego zostaje zakodowana powstałym w ten sposób kluczem mieszanym. - Czas wykonania. Wartość, do jakiej rozwiązuje się
vmBytecodeArrayEncodingKeyGetter— zwrócona przez serwer — jest łączona z tym samym kluczem wewnętrznym, odtwarzanym po stronie klienta na podstawie różnych czynników środowiska uruchomieniowego, w celu zdekodowania kodu bajtowego.
Ponieważ obie strony mieszają klucz użytkownika z kluczem wewnętrznym, getter musi rozwiązywać się do dokładnie tego samego ciągu, który przekazano
jako vmBytecodeArrayEncodingKey. Żaden z elementów nie wystarcza samodzielnie: klucz użytkownika bez klucza wewnętrznego nie pozwala zdekodować
kodu bajtowego, a klucz wewnętrzny bez klucza użytkownika jest bezużyteczny — dlatego właśnie serwowanie klucza wyłącznie uwierzytelnionym wywołaniom
sprawia, że skradziona paczka pozostaje bezużyteczna.
Strona serwera
Endpoint decyduje, który klucz zwrócić, na podstawie tego, czemu ufa dana aplikacja — poprawnej sesji, oczekiwanego
nagłówka Origin lub Referer, weryfikacji licencji i tak dalej. Sztuczka polega na tym, aby zamiast odrzucać niezaufane wywołania, zwracać
błędny klucz. Kod bajtowy dekoduje się wtedy do bezsensownych danych, a chroniony kod psuje się sam z siebie, co jest mniej rzucające się w oczy niż
oczywisty 401, który podpowiada atakującemu dokładnie to, co należy obejść.
// 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
);
});
