Înapoi la Noutăți
Amenințări

Gheist: token-uri GitHub furate transformă log-urile Actions într-un canal de exfiltrare a secretelor

O campanie automatizată folosește token-uri GitHub furate pentru a planta un workflow dintr-un singur fișier care scrie toate secretele unui repository în propriul log Actions, apoi descarcă log-ul și șterge execuția. Cu peste 13.500 de ramuri plantate în peste 850 de conturi, expunerea se concentrează în repository-urile private și pe GitHub Enterprise Server, unde protecția GitHub pentru execuții suspecte nu se aplică.

Gheist: token-uri GitHub furate transformă log-urile Actions într-un canal de exfiltrare a secretelor

Gheist: token-uri GitHub furate transformă log-urile Actions într-un canal de exfiltrare a secretelor

O campanie complet automatizată, urmărită sub numele Gheist, folosește token-uri GitHub furate pentru a planta un workflow format dintr-un singur fișier în fiecare repository în care acele token-uri au drept de scriere. Workflow-ul scrie toate secretele pe care repository-ul le poate citi în propriul log GitHub Actions, atacatorii descarcă log-ul prin API-ul GitHub, iar execuția este ștearsă câteva secunde mai târziu. Niciun server al atacatorilor nu este contactat: secretele ies chiar prin GitHub.

Operațiunea rulează cel puțin de la jumătatea lunii septembrie și era încă activă și în evoluție la 24 octombrie. Au fost identificate peste 13.500 de ramuri plantate în peste 850 de conturi compromise, de la proiecte personale până la repository-urile private ale unor organizații enterprise. Nu se știe câte dintre aceste organizații au pierdut efectiv secrete.

Cum funcționează atacul

Instrumentul are nevoie de un singur lucru: un token GitHub cu drept de scriere într-un repository. Cu el, secvența este mereu aceeași.

  1. Plantarea ramurii. O ramură nouă, numită ci-sync- urmat de șase caractere aleatorii, este împinsă în repository. Conține un singur commit, cu mesajul ci: sync config, care adaugă un singur fișier: .github/workflows/sync-check.yml. Commit-ul nu este semnat și poartă numele proprietarului token-ului. Push-ul în sine declanșează workflow-ul.
  2. Extragerea secretelor. Workflow-ul afișează mediul job-ului și fiecare secret pe care job-ul îl poate citi, codificate în base32. GitHub maschează valorile secretelor în log-uri, însă mascarea nu mai este garantată după ce o valoare a fost codificată sau transformată. Textul base32 nu mai corespunde secretului stocat, așa că ajunge în log în întregime.
  3. Colectare și ștergere. Instrumentul descarcă log-ul workflow-ului prin API și șterge execuția în câteva secunde.

Două detalii fac această variantă mai greu de observat și de izolat. Workflow-ul declară un bloc permissions gol, care restrânge GITHUB_TOKEN-ul job-ului (de care atacatorii nu au nevoie), dar nu îl împiedică deloc să citească secretele. Iar o versiune a instrumentului rulează un job suplimentar pentru fiecare mediu de deployment configurat în repository, astfel încât și secretele limitate la acele medii, de regulă credențiale de producție, sunt afișate.

Ștergerea ramurii sau a execuției nu anulează expunerea. Până când dispare oricare dintre ele, log-ul a fost deja descărcat.

Campania își ia numele de la șirul user agent gheist, pe care instrumentul îl trimite cu fiecare cerere către API-ul GitHub.

De ce riscul real este în repository-urile private și pe Enterprise Server

Din 28 iulie, GitHub reține execuțiile de workflow pe care le identifică drept potențial malițioase până când un colaborator cu drept de scriere le aprobă. Fiecare execuție Gheist examinată până acum în repository-uri publice, indiferent de versiunea instrumentului, era încă blocată în această așteptare, deci payload-ul nu a rulat niciodată.

Această protecție se aplică în prezent doar repository-urilor publice de pe github.com. Nu acoperă repository-urile private, iar GitHub Enterprise Server nu o are deloc. În aceste medii, tabloul este diferit:

  • Log-urile de audit GitHub arată execuții pe ramurile plantate care se finalizează și sunt apoi șterse de user agent-ul gheist.
  • Înregistrările AWS CloudTrail arată că și workflow-urile proprii ale organizației au rulat pe ramurile ci-sync- și și-au asumat roluri AWS, ceea ce confirmă că Actions a rulat acolo fără niciun pas de aprobare.
  • În organizațiile enterprise, instrumentul a trecut în câteva minute prin token-urile mai multor utilizatori și conturi de automatizare, clonând repository-uri private înainte de a planta workflow-ul. Câteva ore mai târziu, credențiale cloud din aceeași organizație au fost folosite pentru a enumera AWS Secrets Manager. De unde proveneau acele credențiale și cine le-a folosit nu a fost stabilit.

În plus, reținerea prinde doar execuțiile pe care euristicile GitHub le semnalează. O campanie separată, GhostAction, a continuat să ruleze în repository-uri publice până când GitHub a început să o blocheze pe 9 octombrie. Gheist este distinctă de GhostAction și folosește alte fișiere de workflow.

O nouă variantă pentru runner-e self-hosted

În jurul datei de 19 octombrie, o versiune a instrumentului a început să împingă un al doilea job destinat runner-elor self-hosted, mașinile pe care organizațiile le operează singure pentru a procesa job-uri Actions, și pare să vizeze doar repository-urile care au un astfel de runner înregistrat. Job-ul caută pe runner credențiale din 31 de tipuri de token-uri: chei private, variabile de mediu ale proceselor, credențiale de rol ale instanțelor AWS, fișiere de credențiale ale runner-ului și token-ul contului de serviciu Kubernetes. Deocamdată pare să aibă erori și nu a fost observată nicio execuție reușită, dar intenția este clară: pivotarea de la secretele CI la infrastructura din spatele runner-ului.

De unde provin token-urile și cine le mai folosește

Token-urile furate care au putut fi urmărite provin din log-uri de infostealer și din token-uri expuse în incidente anterioare de supply chain, inclusiv compromiterea scanner-ului de vulnerabilități Trivy din martie. Aceleași conturi compromise sunt împărțite cu alți operatori:

  • Pe 6 octombrie, 391 de conturi, multe dintre ele folosite și de Gheist, au redenumit aproximativ 1.461 de repository-uri pentru a părea software piratat și a distribui malware. Redenumirea a șters 610 dintre ramurile plantate.
  • Unele victime par să fie infectate și cu PolinRider, un malware asociat cu operatori nord-coreeni. Pe 19 și 20 octombrie, trei dintre ele au re-împins commit-urile Gheist cu un loader adăugat într-un fișier vite.config.js.
  • Cincizeci și șapte dintre conturi fuseseră deja lovite de Megalodon, o campanie care a împins workflow-uri de furt de secrete în mii de repository-uri GitHub în luna mai.

Cum vă verificați repository-urile

  • Căutați ramuri de forma ci-sync- plus șase caractere, în care singura modificare este un fișier nou .github/workflows/sync-check.yml cu mesajul de commit ci: sync config.
  • În log-urile de audit GitHub, căutați execuții de workflow pe ramuri ci-sync-* șterse în mai puțin de un minut de la finalizare, precum și orice activitate a user agent-ului gheist.
  • Dacă workflow-urile voastre se autentifică la un furnizor cloud prin OIDC, căutați în log-urile de audit cloud asumări de rol sau schimburi de token-uri al căror subiect include o ramură ci-sync-. Metoda funcționează chiar dacă nu păstrați log-urile GitHub. Rețineți că, implicit, subiectul OIDC include numele ramurii doar când job-ul nu folosește un mediu de deployment.
  • Pe runner-ele self-hosted, căutați fișierele .gdmp_pat, .gdmp_patg și .gdmp_sweep, precum și șirul gdmp-pivot în log-urile job-urilor.

Indicatori pentru instrumentele voastre de detecție:

User agent:                 gheist
Branch:                     ^ci-sync-[0-9a-z]{6}$
Commit message:             ci: sync config   (adds only .github/workflows/sync-check.yml)
Log string:                 toJSON(secrets) }}' | base32 -w 120
Log string:                 ===== gdmp-pivot:
Files (runner):             .gdmp_pat  .gdmp_patg  .gdmp_sweep
Blob SHA (plain):           f3da2b8b357dcda02fd455d314be2a95007516a6
Blob SHA (timeout):         d09535857a741bbb976bdc084a9a021713133291
Blob SHA (separator):       1dc5af9cce950aad1eb1546ecce8565b117540c4
Blob SHA (self-hosted job): c665b778b2d822453293003e7ca0bbb2cb08e39d

Versiunile workflow-ului care enumeră mediile de deployment ale unui repository diferă de la un repository la altul și, prin urmare, nu au un hash fix.

Ce faceți dacă găsiți o ramură ci-sync

  • Rotiți toate secretele de repository, de organizație și de mediu la care repository-ul putea ajunge. Presupuneți că toate au fost citite.
  • Revocați token-urile care au împins commit-urile și verificați dispozitivele proprietarilor pentru malware de tip infostealer.
  • Dacă o ramură ci-sync- a rulat pe un runner self-hosted, tratați mașina respectivă și rolul instanței sale cloud ca fiind compromise.
  • Nu aprobați execuțiile sync-check reținute și nu rulați cod din ramurile ci-sync-.

Cum faceți un token furat să valoreze mai puțin

  • Cereți revizori pentru mediile care conțin secrete de producție. Un job nu poate citi secretele unui mediu până când un revizor obligatoriu nu îl aprobă.
  • Restrângeți politicile de încredere OIDC la ramuri sau medii protejate, astfel încât o ramură creată de un atacator să nu poată asuma rolurile voastre cloud.
  • Preferați GitHub Apps sau token-uri fine-grained, cu durată scurtă, în locul token-urilor personale de acces cu viață lungă.
  • Rulați runner-ele self-hosted ca mașini efemere, fără acces la socket-ul Docker și fără acces deschis la metadatele cloud.

Gheist ne reamintește că o platformă CI este un seif de credențiale cu un buton de build atașat. Rădăcina acestei campanii nu este o vulnerabilitate GitHub, ci un flux constant de token-uri de dezvoltator colectate de infostealere și scurse în breșe anterioare. Să afli când token-urile organizației tale apar în acele log-uri de stealer și în dump-urile scurse, înainte ca cineva să le folosească automatizat, este exact ceea ce serviciul nostru de monitorizare Deep/Dark Web este construit să detecteze.

DISTRIBUIE