LW IT Solutions
« Blog Overview /Tworzenie stron internetowych/Tutorials / Tutorial: Odczyt zminifikowanego skryptu obcego, zanim trafi...
This post in other languages:

Tutorial: Odczyt zminifikowanego skryptu obcego, zanim trafi na stronę

Tutorial: Odczyt zminifikowanego skryptu obcego, zanim trafi na stronę
Spis treści
  1. Co formatowanie przywraca, a czego nie
  2. Sześć rzeczy wartych wyszukania
  3. Odróżnienie zminifikowanego od zaciemnionego
  4. Wyciągnięcie listy hostów
  5. Przypięcie i miejsce, w którym zawodzi
  6. Co pokazuje dopiero obserwacja
  7. Pytania i odpowiedzi
  8. Źródła

Dostawca przysyła fragment kodu, ktoś wkleja go do menedżera tagów i od tej chwili na każdej stronie działa plik, którego nikt nie przeczytał, z pełnym dostępem do wszystkiego, co strona zawiera.

Porządne przeczytanie go jest pracą przeglądu bezpieczeństwa. Przeczytanie na tyle dobrze, by rozstrzygnąć, to sześć przeszukań sformatowanej kopii i kosztuje około dwudziestu minut.

Sześć grup zdolności z oznaczeniem znaleziska i pasującym wzorcem, obok wyciągnięta z tego samego pliku lista hostów i zbudowana z niej dyrektywa CSP
Sześć przeszukań opisuje, co skrypt potrafi. Lista hostów po prawej pochodzi z tego samego pliku i jest polityką bezpieczeństwa, której skrypt wymaga.

Co formatowanie przywraca, a czego nie

Minifikacja usuwa końce linii i wcięcia, skraca nazwy zmiennych lokalnych do jednego lub dwóch znaków i wyrzuca komentarze. Formatowanie cofa pierwsze z tego i nic poza tym.

To wystarcza, bo części, na których zależy, zachowują nazwy. Właściwości obiektu przeglądarki – localStorage, fetch, navigator – nie da się przemianować, bo przeglądarka przestałaby ją rozpoznawać. Każda zdolność skryptu stoi więc nadal wypisana w pełni, otoczona zmiennymi o nazwach a, t i e.

# pobrac kopie i sformatowac
curl -s https://cdn.anbieter.example/widget.js -o widget.min.js
npx js-beautify widget.min.js -o widget.js

# rozmiar przed i po mowi, jak mocno bylo zageszczone
wc -c widget.min.js widget.js

Przeglądarka potrafi to samo bez narzędzia: panel źródeł ma przycisk formatowania rozwijający plik na miejscu – droga szybsza przy krótkim spojrzeniu i błędna przy przeglądzie dokumentowanym, bo nie zostaje żaden plik, z którym dałoby się porównać następnym razem.

Sześć rzeczy wartych wyszukania

Każde z tych przeszukań odpowiada na jedno pytanie, a razem opisują, co skrypt jest w stanie zrobić.

# 1 czy cos wysyla na zewnatrz?
grep -nE 'fetch\(|XMLHttpRequest|sendBeacon|new Image|WebSocket' widget.js

# 2 czy cos zapisuje trwale?
grep -nE 'localStorage|sessionStorage|document\.cookie|indexedDB' widget.js

# 3 czy doladowuje dalszy kod?
grep -nE "createElement\(['\"]script|\\.src\\s*=|import\\(" widget.js

# 4 czy podsluchuje wpisywanie?
grep -nE "addEventListener\\(['\"](input|keydown|keyup|change|submit)" widget.js

# 5 czy zbiera cechy urzadzenia?
grep -nE 'getContext|AudioContext|navigator\.(plugins|hardwareConcurrency)|RTCPeerConnection' widget.js

# 6 dokad to idzie?
grep -oE 'https?://[a-z0-9.-]+' widget.js | sort -u

Dwa pierwsze rozstrzygają, czy w ogóle potrzebna jest kategoria zgody. Skrypt, który nic nie przechowuje i nic nie wysyła, jest pomocą wyświetlającą; taki, który robi jedno i drugie, jest przetwarzaniem danych – a ta różnica należy do polityki prywatności, a nie do zgłoszenia.

Czwarty zasługuje na szczególną uwagę przy wszystkim osadzonym w pobliżu formularza. Nasłuch na input nie jest automatycznie rejestrowaniem klawiszy – okienko czatu słusznie słucha własnego pola – lecz sprawdzeniem jest to, do którego elementu został przypięty, a to stoi kilka linii niżej.

Piąty to grupa cech rozpoznawczych. Wywołanie canvas w widgecie, który coś rysuje, jest spodziewane; wywołanie canvas we fragmencie analitycznym, który nie wyświetla nic widocznego, ma jeden cel – a ten cel niesie skutki dla zgody, niezależnie od tego, jak nazywa go dostawca.

Odróżnienie zminifikowanego od zaciemnionego

To różne rzeczy, a różnica jest punktem rozstrzygnięcia, a nie subtelnością.

Minifikacja jest optymalizacją rozmiaru i zostawia czytelną strukturę. Zaciemnianie jest pomyślane tak, by uniemożliwić czytanie, i ma rozpoznawalne cechy: dużą listę ciągów zakodowanych szesnastkowo na początku pliku, funkcję indeksującą wyszukującą w niej, String.fromCharCode w pętli, atob wokół czegoś, co nie jest obrazem, a przede wszystkim eval albo new Function na złożonym ciągu znaków.

grep -nE 'eval\(|new Function\(|atob\(|String\.fromCharCode|\\\\x[0-9a-f]{2}' widget.js

Trafienie w tym miejscu nie dowodzi złej woli – część dostawców zaciemnia, by chronić własną logikę. Oznacza jednak, że sześć powyższych przeszukań przestaje odpowiadać na pytanie, bo zdolności dają się złożyć w czasie działania z ciągów, których w takiej postaci w pliku nie ma.

Skutek praktyczny: zaciemnionego skryptu obcego nie da się sprawdzić przez czytanie. Zostaje zachowanie – wczytać w stronie testowej, zapisać panel sieci i panel pamięci i porównać z tym, co podaje dostawca. Jeżeli to nie jest do przyjęcia, odpowiedzią nie jest „lepiej szukać”, lecz „inny dostawca”.

Wyciągnięcie listy hostów

Szóste przeszukanie daje wynik najbardziej użyteczny od razu: każdą domenę wymienioną w pliku. Ta lista należy do polityki bezpieczeństwa, a zbudowanie jej z pliku zamiast z dokumentacji wyłapuje punkty końcowe, o których dokumentacja zapomniała.

grep -oE 'https?://[a-z0-9.-]+' widget.js | sort -u

https://cdn.anbieter.example
https://api.anbieter.example
https://events.anbieter.example
https://fonts.gstatic.com

Dwa z czterech są tymi ciekawymi. Host pojawiający się wyłącznie w komentarzu albo w ciągu dokumentacyjnym jest szumem; host w wywołaniu fetch jest celem. Sprawdzenie, który jest który, kosztuje po jednym dodatkowym przeszukaniu na host i jest różnicą między polityką działającą a przepisaną z wpisu na blogu.

Content-Security-Policy:
  script-src  'self' https://cdn.anbieter.example;
  connect-src 'self' https://api.anbieter.example https://events.anbieter.example;
  font-src    'self' https://fonts.gstatic.com;

Rozpoczęcie w trybie samego raportowania warte jest tego dodatkowego tygodnia. Zgłoszenia wymieniają każdy host przeoczony przez analizę – a taki zawsze się znajdzie, bo skrypt doładowujący drugi skrypt dziedziczy jego cele, a tych w pierwszym pliku nie ma.

Przypięcie i miejsce, w którym zawodzi

Wszystko powyższe opisuje jedną wersję jednego pliku. Dostawca może ją jutro wymienić i nic w witrynie tego nie zauważy.

Przy pliku statycznym lukę tę domyka Subresource Integrity: suma kontrolna w znaczniku skryptu, a przeglądarka odrzuca plik do niej niepasujący. Zmieniony plik przestaje się wtedy wczytywać, zamiast działać niesprawdzony – i to jest właściwa porażka.

<script src="https://cdn.anbieter.example/widget.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>

Przy kontenerze tagów to nie działa, a powód jest zasadniczy: kontener ma się zmieniać, gdy tylko ktoś opublikuje. Suma kontrolna musiałaby być uzupełniana przy każdej publikacji, czego nikt nie robi – kontener wczytywany jest więc bez sprawdzania integralności albo wcale.

To, co wchodzi tam na jego miejsce, jest pytaniem o odpowiedzialność, a nie technicznym: kto może publikować i czy na zmianę patrzy druga osoba. To odpowiedź uczciwa i warto zapisać ją właśnie tak, zamiast udawać, że pokrywa ją nagłówek.

Co pokazuje dopiero obserwacja

Trzy rzeczy umykają czytaniu pliku, a wszystkie trzy pojawiają się w obserwacji, a nie w analizie.

Pierwszą jest skrypt doładowany. Ładowacz pobierający właściwą zawartość w czasie działania nie zawiera żadnej ze zdolności, które ta zawartość ma – sześć przeszukań wraca czysto z pliku, który nie robi nic poza pobraniem ciekawej części. Dyrektywa script-src bez symbolu wieloznacznego czyni to widocznym, bo drugi plik potrzebuje własnego wpisu.

Drugą jest zachowanie warunkowe. Skrypt zbierający dane tylko na określonych stronach, w określonych krajach albo od określonej daty jest w pełni czytelny i nie daje powodu, by zajrzeć akurat w rozstrzygającą gałąź. Znajduje to wyłącznie obserwacja w czasie, a cotygodniowe zestawienie z zachowaną kopią jest jej najtańszą postacią.

curl -s https://cdn.anbieter.example/widget.js | sha256sum
# porownac z zapisana wartoscia, co tydzien

Trzecią jest to, co dostawca robi z danymi po ich dotarciu, a na to nie odpowie żaden plik po tej stronie. To należy do umowy – a przegląd techniczny jest tym, co czyni umowę dostatecznie konkretną, by chcieć ją podpisać: wymienia punkty końcowe, klucze pamięci i zdarzenia, i zamienia ogólne zapewnienie w listę, do której da się kogoś przypiąć.

Pytania i odpowiedzi

Czy Subresource Integrity chroni także skrypt, który ładowacz doładowuje w czasie działania?

Nie. Suma kontrolna dotyczy tylko tego jednego znacznika skryptu, w którym się znajduje. Gdy ładowacz w czasie działania tworzy nowy element skryptu, przeglądarka sprawdza jego plik tylko wtedy, gdy sam ładowacz poda atrybut integrity, a zwykle tego nie robi. Przypięty ładowacz może więc pozostać bez zmian, podczas gdy zawartość, którą pobiera, może być każdego dnia inna.

Tę lukę domyka w tym miejscu polityka bezpieczeństwa, a nie suma kontrolna: dyrektywa script-src bez symbolu wieloznacznego pozwala wczytać drugi plik tylko z hosta wpisanego wprost. Sama zawartość jest wtedy osobnym plikiem: przechodzi przez te same sześć przeszukań co ładowacz, a jej suma kontrolna trafia do cotygodniowego porównania.

Do czego służy atrybut crossorigin w znaczniku skryptu z sumą kontrolną?

Plik z obcego hosta da się sprawdzić tylko wtedy, gdy przeglądarka pobiera go przez CORS; bez tego trybu jego zawartość pozostaje dla kontroli niedostępna, a przeglądarka odrzuca skrypt. crossorigin="anonymous" włącza ten tryb i nie wysyła przy tym ciasteczek. Warunkiem jest to, że dostawca serwuje plik z pasującym nagłówkiem Access-Control-Allow-Origin; jeśli go brakuje, skrypt nie wczytuje się mimo poprawnej sumy kontrolnej.

Co należy zrobić, gdy cotygodniowe porównanie sumy kontrolnej wykaże różnicę?

Inna suma kontrolna mówi tylko, że plik się zmienił, a nie co się zmieniło. Sensowna jest stała kolejność:

  1. Pobrać nową wersję, sformatować ją i odłożyć obok starej. Porównanie obu sformatowanych kopii linia po linii pomaga tylko w ograniczonym stopniu, bo nowy przebieg minifikacji może nawet przy drobnej zmianie na nowo rozdać krótkie nazwy zmiennych i w ten sposób zmienić niemal każdą linię.
  2. Powtórzyć sześć przeszukań i wyszukiwanie zaciemnienia na nowej wersji, a trafienia zestawić z trafieniami starej. Nowa zdolność, na przykład pojawiające się po raz pierwszy sendBeacon albo dostęp do document.cookie, jest ustaleniem, o które chodzi.
  3. Ponownie wyciągnąć listę hostów i porównać ją z polityką bezpieczeństwa. Nowy host należy albo dopisać do polityki, albo wyjaśnić z dostawcą.
  4. Zapisać nową wartość i odnotować, kiedy i dlaczego została przyjęta.

Jeśli skrypt jest osadzony z Subresource Integrity, po zmianie i tak przestaje się wczytywać, dopóki suma kontrolna w znaczniku skryptu nie zostanie zaktualizowana. O tym, czy zostanie zaktualizowana, rozstrzyga wtedy właśnie ta kontrola.

Źródła

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Napisanie komentarza

Przypadki brzegowe pominięte w artykule i pytania o kod są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.

ALL ARTICLES & CATEGORIES

CCTV

Śledź tę kategorię przez RSS

Cloud & AI

Śledź tę kategorię przez RSS

Data Privacy

Wszystkie artykuły w tej kategorii (14) Śledź tę kategorię przez RSS

Digital Analytics

Wszystkie artykuły w tej kategorii (50) Śledź tę kategorię przez RSS

Digital Marketing

Wszystkie artykuły w tej kategorii (34) Śledź tę kategorię przez RSS

IT & Networks

Wszystkie artykuły w tej kategorii (17) Śledź tę kategorię przez RSS

Music Production

Śledź tę kategorię przez RSS

Raspberry Pi

Śledź tę kategorię przez RSS

Smart Home

Wszystkie artykuły w tej kategorii (18) Śledź tę kategorię przez RSS

Tworzenie stron internetowych

Śledź tę kategorię przez RSS

Wtyczki i triki WordPress

Śledź tę kategorię przez RSS