← Blog
Bezpieczeństwo15 min czytania

gowitness — masowe zrzuty ekranu stron podczas rekonesansu

gowitness robi zrzuty ekranu tysięcy stron przez headless Chrome, czyta wyniki Nmapa i Nessusa, a wizualne duplikaty grupuje haszowaniem percepcyjnym. Piszemy też o tym, czego wymaga legalne użycie i co znajduje się w tej bazie.

Wyobraź sobie sytuację, którą zna każdy, kto robił audyt większej infrastruktury. Skan portów zwrócił cztery tysiące adresów z otwartym portem 80 albo 443. Co dalej? Otwieranie ich po kolei w przeglądarce to tygodnie pracy. A odpowiedź na pytanie „które z tych czterech tysięcy są interesujące" trzeba mieć na drugi dzień.

gowitness rozwiązuje to najprostszym możliwym sposobem: robi zrzut ekranu każdego z nich i pokazuje wyniki w galerii. Człowiek przewija galerię i w kilka minut wyłapuje panel administracyjny bez hasła, zapomniane środowisko testowe i drukarkę z interfejsem webowym. Oko robi to lepiej niż jakikolwiek automat.

Najpierw sprawa upoważnienia

To narzędzie odpytuje cudze serwery, więc zaczynamy od zakresu, w jakim wolno je uruchamiać. Wymieniam to na początku, a nie w przypisie, bo to nie formalność.

gowitness jest przeznaczony do trzech sytuacji: badania własnej infrastruktury, wykonywania zleconego testu bezpieczeństwa w zakresie ustalonym na piśmie oraz udziału w programie zgłaszania podatności, w granicach zakresu opublikowanego przez jego organizatora. Poza nimi się go nie uruchamia.

Skanowanie cudzych systemów bez zgody nie jest w polskim porządku prawnym neutralną czynnością techniczną i może wypełniać znamiona czynu zabronionego, niezależnie od tego, czy cokolwiek zostało uszkodzone. Do tego dochodzą warunki świadczenia usług dostawców i regulaminy usług chmurowych. Praktyczny standard branżowy jest prosty i warto się go trzymać: pisemne upoważnienie z określonym zakresem adresów i okresem, przed pierwszym uruchomieniem narzędzia. Przy zleceniu od klienta to element umowy, a nie miła praktyka.

Warto też pamiętać o skali. Narzędzie potrafi przejść całą podsieć, więc pomyłka w zakresie nie oznacza jednego niepotrzebnego żądania, a kilka tysięcy — pod adresami, których nie miałeś prawa dotykać.

Stan projektu

Repozytorium: sensepost/gowitness. 4502 gwiazdki, 453 forki, napisane w Go, licencja GPL-3.0. Start w październiku 2017, więc dziewięć lat historii. Otwartych zgłoszeń jest 43. Za projektem stoi SensePost, czyli zespół badawczy znany w branży bezpieczeństwa, a prowadzi go jedna osoba z tego zespołu.

Tempo trzeba opisać uczciwie, bo jest nierówne. Ostatnie commity na gałęzi master pochodzą z 22 kwietnia 2026 — czyli sprzed czterech i pół miesiąca. Ostatnie wydanie, 3.1.1, ukazało się 24 listopada 2025, a poprzedzające je 3.1.0 tydzień wcześniej; wcześniejsza linia 3.0.x to jesień 2024.

Zajrzałem jednak, czego dotyczyły te kwietniowe commity, i to zmienia ocenę: podmiany obrazu Dockera na wariant z headless Chrome oraz poprawy wykrywania Chrome. Innymi słowy opiekun robi dokładnie tę pracę, która przy takim narzędziu jest niezbędna — nadąża za przeglądarką, na której całe narzędzie stoi. Chrome zmienia się co kilka tygodni i to on jest tu ruchomym elementem, nie kod. Projekt jest więc wolny w wydawaniu, ale utrzymywany w miejscu, które ma znaczenie.

Pięć rodzajów wejścia

Tu jest największa zaleta praktyczna, bo narzędzie wpasowuje się w istniejący warsztat zamiast wymuszać przepisywanie danych. Katalog poleceń pokazuje pięć trybów skanowania:

  • scan single — jeden adres, do sprawdzenia czegoś na szybko.
  • scan file — lista adresów z pliku.
  • scan cidr — cała podsieć podana notacją CIDR.
  • scan nmap — czyta wyniki Nmapa wprost z pliku XML.
  • scan nessus — czyta wyniki skanera Nessus.

Dwa ostatnie tryby są tym, co odróżnia narzędzie dojrzałe od skryptu. Typowy przebieg audytu to najpierw Nmap albo Nessus, a potem pytanie „co właściwie stoi pod tymi portami". Skoro gowitness czyta te wyniki bezpośrednio, nie trzeba pisać pośrednika wyciągającego adresy i porty — a to jest właśnie ten kawałek pracy, który każdy pisał sobie sam i za każdym razem inaczej.

Pierwsze uruchomienie wygląda tak:

go install github.com/sensepost/gowitness@latest

gowitness scan single --url "https://twoja-domena.pl" --write-db

Flaga --write-db jest tu istotna, bo bez niej dostajesz same obrazy, a z nią całą resztę danych i możliwość skorzystania z przeglądarki wyników.

Co zbiera poza obrazem

Zrzut ekranu jest głównym produktem, ale narzędzie zapisuje przy okazji rzeczy, które przy analizie bywają cenniejsze: dziennik żądań, dzienniki konsoli przeglądarki, nagłówki odpowiedzi oraz ciasteczka.

Warto rozumieć, dlaczego to ma znaczenie. Nagłówki mówią, co stoi pod adresem i w jakiej wersji. Dziennik żądań pokazuje, gdzie strona sięga po zasoby — czyli ujawnia usługi trzecie i czasem wewnętrzne adresy. Dziennik konsoli wyłapuje błędy JavaScriptu, które nierzadko zawierają ścieżki, nazwy zmiennych albo adresy API. To wszystko dane, które przy ręcznym przeglądaniu galerii i tak byś obejrzał, tylko po jednej stronie na raz.

Haszowanie percepcyjne, czyli dlaczego to działa przy tysiącach adresów

To najciekawszy element techniczny i najłatwiejszy do przeoczenia, bo widać go tylko w liście zależności — jest tam biblioteka do haszowania percepcyjnego obrazów.

Problem, który to rozwiązuje, ujawnia się dokładnie wtedy, gdy narzędzie jest najbardziej potrzebne. Przeskanowanie podsieci klasy B daje tysiące zrzutów, z których ogromna większość jest identyczna: domyślna strona nginksa, ten sam ekran logowania na dwustu urządzeniach tego samego producenta, ta sama strona błędu. Zwykłe porównanie plików nic nie da, bo obrazy różnią się o kilka pikseli — inny znacznik czasu, inna nazwa hosta w nagłówku.

Hasz percepcyjny liczy natomiast odcisk tego, jak obraz wygląda, a nie z jakich bajtów się składa. Dwa zrzuty tej samej domyślnej strony dają odciski niemal identyczne i dają się zgrupować. Praktyczny skutek: galeria zamiast czterech tysięcy kafelków pokazuje kilkadziesiąt różnych rzeczy, a przegląd skraca się z dnia do kwadransa. To jest cecha, która decyduje o użyteczności narzędzia przy prawdziwej skali.

Druga zależność warta wymienienia to biblioteka odcisku technologii — ta sama klasa rozwiązania co znane rozszerzenia rozpoznające, na czym zbudowana jest strona. Dzięki niej wyniki zawierają nie tylko obraz, ale i informację o wykrytym stosie, co pozwala odfiltrować na przykład wszystko, co działa na konkretnej wersji konkretnego oprogramowania.

Pokrętła, które trzeba znać

Wartości domyślne mówią o tym narzędziu więcej niż README, bo pokazują, że autor pisał je z myślą o pracy na cudzej infrastrukturze, a nie o wyciśnięciu maksymalnej przepustowości.

  • --threads, domyślnie 6 — liczba równoległych wątków. Zaskakująco mało, i to jest właściwa decyzja: sześć równoległych instancji przeglądarki nie przewróci cudzego serwera.
  • --delay, domyślnie 3 sekundy — opóźnienie między wejściem na stronę a zrobieniem zrzutu. Daje stronie czas na dokończenie renderowania, bo zrzut pustego jeszcze ekranu jest bezwartościowy.
  • --timeout, domyślnie 60 sekund na stronę. Przy dużych zakresach warto obniżyć, bo martwe adresy zajmą minutę każdy.
  • --driver, domyślnie chromedp, alternatywnie gorod — oba sterowniki przeglądarki są wybieralne w czasie działania. To wyjaśnia, dlaczego w zależnościach są dwa, i daje drogę wyjścia, gdy jeden przestanie współpracować z bieżącym Chrome.
  • --http-code-filter — domyślnie zrzucane są wszystkie kody odpowiedzi, ale można zawęzić do wybranych. Przy skanie podsieci ograniczenie do kodu 200 odsiewa większość szumu.
  • --screenshot-format, domyślnie jpeg z jakością 60, oraz --screenshot-fullpage, domyślnie wyłączone — standardowo dostajesz sam widoczny obszar, nie całą stronę. Przy tysiącach adresów to świadomy kompromis na rzecz rozmiaru.

Jest też flaga z ostrzeżeniem wpisanym wprost w tekst pomocy: --save-content zapisuje treść pobraną w żądaniach sieciowych, a opis mówi, że ma potencjał do wysadzenia twojego magazynu danych pod względem rozmiaru. Przeciwwagą są --skip-html i --skip-network-logs, którymi te dane można odchudzić.

Osobno warto wymienić --javascript (albo wariant czytający z pliku), pozwalające wykonać własną funkcję JavaScriptu na każdej stronie przed zrzutem. Jest przydatne — na przykład do zamknięcia banera zgody zasłaniającego całą stronę — ale trzeba mieć świadomość, czym jest: wykonywaniem własnego kodu w kontekście cudzej strony. Przy zleceniu w ustalonym zakresie to normalne narzędzie pracy, warto tylko, by mieściło się w tym, na co klient się zgodził.

Do potoków powłoki służy --write-stdout, a jeśli ktoś chce mierzyć wydajność, jest globalna flaga profilowania procesora i pamięci, zapisująca wyniki do katalogu ze znacznikiem czasu.

Baza i formaty wyjścia

Dane można zapisać do bazy SQLite, do formatu JSON Lines albo do CSV. Warto jednak zajrzeć w zależności, bo wynika z nich coś, czego README nie mówi wprost: warstwa dostępu do bazy ma sterowniki do SQLite, MySQL i PostgreSQL.

Przy pracy pojedynczej osoby SQLite wystarcza i jest najprostszy. Ale przy zespole pracującym nad jednym zleceniem możliwość wskazania Postgresa oznacza wspólną bazę wyników zamiast wymiany plików — i to jest różnica organizacyjna, nie tylko techniczna.

Do zarządzania wynikami jest osobna grupa poleceń: report list wypisuje zawartość, report generate tworzy raport, report convert przekłada między formatami, report migrate aktualizuje schemat bazy, a report merge — i to jest pozycja warta uwagi, bo jest najobszerniejszym plikiem w tym katalogu — scala wiele baz w jedną. Przy zleceniu, w którym trzy osoby skanowały trzy zakresy adresów, to właśnie polecenie, którego się szuka na końcu.

Chrome: skąd się bierze i jak go odsunąć

Skoro całe narzędzie stoi na przeglądarce, warto wiedzieć, skąd ona się bierze — a odpowiedź jest w opisie flagi --chrome-path: wskazuje ona ścieżkę do binarki Chrome, a domyślnie pobierana jest binarka odpowiednia dla platformy.

To istotne z dwóch powodów. Operacyjnie: pierwsze uruchomienie ściąga przeglądarkę, więc w środowisku bez dostępu do internetu albo z kontrolowanym łańcuchem dostaw trzeba wskazać własną instalację. Z perspektywy bezpieczeństwa: warto wiedzieć, że narzędzie samo dociąga plik wykonywalny, zamiast założyć, że korzysta z systemowego.

Jest natomiast flaga rozwiązująca problem znacznie poważniejszy. --chrome-wss-url pozwala podłączyć się do zdalnej, już działającej instancji Chrome przez protokół DevTools. Znaczenie tego jest następujące: przeglądarka odwiedzająca cudze, potencjalnie złośliwe strony nie musi działać na twojej maszynie ani w twojej sieci. Możesz ją postawić na osobnej, jednorazowej maszynie i tylko sterować nią zdalnie.

Przy narzędziu rekonesansu jest to właściwa architektura. Przeglądarka wchodząca na tysiące nieznanych adresów jest tą częścią zestawu, która najprawdopodobniej zostanie zaatakowana — a trzymanie jej poza swoją siecią jest tanim zabezpieczeniem. Uzupełnia to --chrome-proxy, przyjmująca proxy HTTP albo SOCKS5, czyli droga do wypuszczenia ruchu przez wskazane wyjście.

Dwie mniejsze rzeczy z tej samej grupy. Rozmiar okna to domyślnie 1280 na 720 pikseli, co określa, ile strony trafi na zrzut w trybie bez pełnej strony. A domyślny ciąg identyfikujący przeglądarkę jest wpisany na stałe i podaje Chrome w wersji 128 na macOS — czyli wersję z 2024 roku. Praktycznie jest to sygnatura rozpoznawalna dla każdego, kto patrzy w dzienniki, i warto o niej wiedzieć niezależnie od tego, czy zamierzasz ją zmienić flagą --chrome-user-agent. Dodatkowe nagłówki dokłada się przez --chrome-header, którą można podać wielokrotnie.

Przeglądarka wyników i jak jej nie wystawić

Polecenie report server uruchamia interfejs webowy z galerią, widokiem szczegółów i — jak podkreśla README — w pełni funkcjonalnym API. To jest właściwe miejsce pracy z wynikami.

I tu dochodzimy do rzeczy, którą autor rozwiązał wzorowo, a którą łatwo zignorować. Dołączony plik docker-compose.yml nie uruchamia po prostu serwera z wynikami. Stawia go za odwrotnym proxy Traefik, z terminowaniem TLS i uwierzytelnianiem podstawowym HTTP, przy ustawieniu, w którym usługi nie są domyślnie wystawiane. W komentarzu jest przy tym wyjaśnienie, jak wygenerować własny skrót hasła narzędziem htpasswd, przypomnienie o konieczności podwojenia znaku dolara w pliku oraz odnośnik do osobnego narzędzia, jeśli ktoś chce zarządzać dostępem porządniej.

Powód takiej konfiguracji jest oczywisty, gdy się o nim pomyśli: ta baza jest zbiorem najwrażliwszych danych w całym zleceniu. Zawiera zrzuty ekranów paneli administracyjnych klienta, jego nagłówki, ciasteczka i mapę tego, co jest wystawione i podatne. Serwer z wynikami wystawiony bez uwierzytelniania jest gotowym raportem dla kogoś, kto go znajdzie. Autor przewidział to i dowiózł bezpieczną konfigurację jako domyślną — co przy narzędziach tej klasy nie jest regułą.

Na zrzutach są dane osobowe

Sekcja, której w dokumentacji narzędzi rekonesansu praktycznie się nie spotyka, a która w polskiej firmie świadczącej usługi audytu jest realnym obowiązkiem.

Zrzut ekranu strony logowania zawiera zwykle niewiele. Ale skan większej infrastruktury złapie też panele, w których widać nazwiska pracowników, wewnętrzne systemy z listami spraw, interfejsy z danymi klientów, a czasem stronę, na której ktoś zostawił otwartą sesję. Do tego dochodzą ciasteczka i nagłówki, które narzędzie zapisuje razem z obrazem.

Wniosek jest praktyczny i sprowadza się do czterech rzeczy. Baza wyników jest zbiorem danych i ma leżeć w miejscu z kontrolą dostępu, nie w katalogu domowym na laptopie bez szyfrowania dysku. Retencja musi być określona — dane rekonesansu po zakończeniu zlecenia i przekazaniu raportu nie mają powodu istnieć, a umowa z klientem powinna to mówić. Przekazywanie wyników — także zrzutów wewnątrz raportu — to udostępnianie danych, więc kanał ma być odpowiedni. I wreszcie, jeśli działasz jako podmiot przetwarzający na zlecenie klienta, ta czynność powinna być objęta umową powierzenia.

Żadna z tych rzeczy nie utrudnia pracy. Wszystkie natomiast bywają pomijane, bo narzędzia rekonesansu bywają traktowane jako techniczne, a nie jako coś, co produkuje zbiór danych.

Pułapki w jednym miejscu

  • Bez pisemnego upoważnienia tego nie uruchamiasz. Zakres adresów i okres, przed pierwszym skanem.
  • Pomyłka w zakresie CIDR to tysiące żądań, nie jedno. Sprawdź zakres dwa razy.
  • Bez flagi --write-db dostajesz same obrazy, bez nagłówków, ciasteczek i możliwości użycia przeglądarki wyników.
  • Serwera wyników nie wystawiaj bez uwierzytelniania. Dołączona konfiguracja Dockera robi to dobrze — użyj jej, zmieniając hasło.
  • Baza wyników zawiera dane osobowe. Kontrola dostępu, określona retencja, właściwy kanał przekazania.
  • Ostatnie wydanie z listopada 2025, przy commitach z kwietnia 2026 — do najnowszych poprawek trzeba budować ze źródeł.
  • Wsparcie dla Windowsa jest opisane jako „w większości działające", w odróżnieniu od Linuksa i macOS.
  • Narzędzie stoi na headless Chrome, więc wymaga jego obecności i jest wrażliwe na jego wersję — kwietniowe commity dotyczyły właśnie wykrywania przeglądarki.
  • Domyślnie narzędzie pobiera binarkę Chrome — w środowisku bez internetu wskaż własną przez --chrome-path.
  • Domyślny identyfikator przeglądarki podaje Chrome 128 na macOS, czyli wersję z 2024. To rozpoznawalna sygnatura w dziennikach.
  • Flaga --save-content potrafi wysadzić magazyn danych — ostrzeżenie jest w samym tekście pomocy.
  • Rozważ zdalną przeglądarkę przez --chrome-wss-url — Chrome odwiedzający nieznane adresy nie powinien działać w twojej sieci.
  • Licencja GPL-3.0. Osadzenie tego kodu w zamkniętym produkcie wymaga rozmowy z SensePost.

Podsumowanie

gowitness jest narzędziem wąskim i właśnie dlatego dobrym. Robi jedną rzecz — zrzuty ekranu stron na dużą skalę — i robi ją z całą obudową, której się przy tej skali potrzebuje: czyta wyniki innych skanerów, grupuje wizualne duplikaty, zapisuje dane towarzyszące i daje przeglądarkę z API.

  • Pięć rodzajów wejścia, w tym czytanie wyników Nmapa i Nessusa wprost, bez pisania pośrednika.
  • Grupowanie wizualnych duplikatów haszowaniem percepcyjnym — cecha decydująca o użyteczności przy tysiącach adresów.
  • Odcisk technologii i dane towarzyszące — nagłówki, ciasteczka, dzienniki żądań i konsoli obok obrazu.
  • Trzy silniki bazy — SQLite dla jednej osoby, Postgres albo MySQL dla zespołu pracującego na wspólnych wynikach.
  • Polecenie scalania baz, czyli odpowiedź na zlecenie dzielone między kilka osób.
  • Bezpieczna domyślna konfiguracja wdrożenia — przeglądarka wyników za proxy z TLS i uwierzytelnianiem, z instrukcją zmiany hasła.

Projekt jest wydany na licencji GPL-3.0, a sekcja licencyjna README dodaje zdanie warte uwagi: uprawnienia wykraczające poza zakres tej licencji mogą być dostępne, z odnośnikiem do kontaktu z SensePost. Czyli standardowy układ podwójnego licencjonowania — do własnego użytku i do pracy audytorskiej GPL nie stanowi żadnej przeszkody, bo używasz narzędzia, a nie rozpowszechniasz go. Gdybyś natomiast chciał wbudować ten kod w zamknięty produkt komercyjny — na przykład we własną platformę do zarządzania podatnościami — to jest właśnie ta rozmowa, którą trzeba odbyć przed napisaniem pierwszej linii integracji, a nie po.