Hub documentation

Pemindaian Pickle

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Pemindaian Pickle

Pickle adalah format serialisasi yang banyak digunakan dalam ML. Yang paling menonjol, format ini merupakan format default untuk bobot model PyTorch.

Terdapat serangan eksekusi kode arbitrer yang berbahaya yang dapat dilakukan saat Anda memuat file pickle. Kami menyarankan untuk memuat model dari pengguna dan organisasi yang Anda percaya, mengandalkan commit yang ditandatangani, dan/atau memuat model dari format TF atau Jax dengan mekanisme konversi otomatis from_tf=True. Kami juga memitigasi masalah ini dengan menampilkan/"memverifikasi" daftar impor di setiap file pickle, langsung di Hub. Terakhir, kami sedang bereksperimen dengan format serialisasi baru yang sederhana untuk bobot yang disebut safetensors.

Apa itu pickle?

Dari dokumentasi resmi :

Modul pickle mengimplementasikan protokol biner untuk serialisasi dan deserialisasi struktur objek Python.

Artinya, pickle adalah protokol serialisasi, sesuatu yang Anda gunakan untuk berbagi data secara efisien di antara berbagai pihak.

Kami menyebut pickle sebagai file biner yang dihasilkan saat proses pickling.

Pada intinya, pickle pada dasarnya adalah tumpukan instruksi atau opcode. Seperti yang mungkin Anda tebak, format ini tidak dapat dibaca oleh manusia. Opcode dihasilkan saat proses pickling dan dibaca secara berurutan saat proses unpickling. Berdasarkan opcode, aksi tertentu akan dieksekusi.

Berikut adalah contoh kecil:

import pickle
import pickletools

var = "data I want to share with a friend"

# store the pickle data in a file named 'payload.pkl'
with open('payload.pkl', 'wb') as f:
    pickle.dump(var, f)

# disassemble the pickle
# and print the instructions to the command line
with open('payload.pkl', 'rb') as f:
    pickletools.dis(f)

Saat Anda menjalankan ini, file pickle akan dibuat dan instruksi berikut akan dicetak di terminal Anda:

    0: \x80 PROTO      4
    2: \x95 FRAME      48
   11: \x8c SHORT_BINUNICODE 'data I want to share with a friend'
   57: \x94 MEMOIZE    (as 0)
   58: .    STOP
highest protocol among opcodes = 4

Jangan terlalu khawatir tentang instruksi untuk saat ini, cukup ketahui bahwa modul pickletools sangat berguna untuk menganalisis pickle. Modul ini memungkinkan Anda membaca instruksi dalam file tanpa mengeksekusi kode apa pun.

Pickle bukan sekadar protokol serialisasi; ia memberikan fleksibilitas lebih dengan memungkinkan pengguna untuk menjalankan kode Python pada saat de-serialisasi. Kedengarannya tidak baik, bukan?

Mengapa hal ini berbahaya?

Seperti yang telah kami sebutkan di atas, de-serialisasi pickle berarti kode dapat dieksekusi. Namun, hal ini memiliki beberapa keterbatasan: Anda hanya dapat merujuk fungsi dan kelas dari modul tingkat atas; Anda tidak dapat menyematkannya di dalam file pickle itu sendiri.

Kembali ke papan gambar:

import pickle
import pickletools

class Data:
    def __init__(self, important_stuff: str):
        self.important_stuff = important_stuff

d = Data("42")

with open('payload.pkl', 'wb') as f:
    pickle.dump(d, f)

Saat kami menjalankan skrip ini, kami mendapatkan payload.pkl lagi. Saat kami memeriksa isi file:


# cat payload.pkl
__main__Data)}important_stuff42sb.%

# hexyl payload.pkl
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚00000000β”‚ 80 04 95 33 00 00 00 00 β”Š 00 00 00 8c 08 5f 5f 6d β”‚Γ—β€’Γ—30000β”Š000Γ—β€’__mβ”‚
β”‚00000010β”‚ 61 69 6e 5f 5f 94 8c 04 β”Š 44 61 74 61 94 93 94 29 β”‚ain__Γ—Γ—β€’β”ŠDataΓ—Γ—Γ—)β”‚
β”‚00000020β”‚ 81 94 7d 94 8c 0f 69 6d β”Š 70 6f 72 74 61 6e 74 5f β”‚Γ—Γ—}Γ—Γ—β€’imβ”Športant_β”‚
β”‚00000030β”‚ 73 74 75 66 66 94 8c 02 β”Š 34 32 94 73 62 2e       β”‚stuffΓ—Γ—β€’β”Š42Γ—sb.  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Kita dapat melihat bahwa tidak ada banyak di dalamnya, hanya beberapa opcode dan data terkait. Anda mungkin berpikir, jadi apa masalahnya dengan pickle?

Mari kita coba hal lain:

from fickling.pickle import Pickled
import pickle

# Create a malicious pickle
data = "my friend needs to know this"

pickle_bin = pickle.dumps(data)

p = Pickled.load(pickle_bin)

p.insert_python_exec('print("you\'ve been pwned !")')

with open('payload.pkl', 'wb') as f:
    p.dump(f)

# innocently unpickle and get your friend's data
with open('payload.pkl', 'rb') as f:
    data = pickle.load(f)
    print(data)

Di sini kami menggunakan pustaka fickling untuk kesederhanaan. Pustaka ini memungkinkan kami menambahkan instruksi pickle untuk mengeksekusi kode yang terkandung dalam sebuah string melalui fungsi exec. Inilah cara Anda menghindari fakta bahwa Anda tidak dapat mendefinisikan fungsi atau kelas di pickle Anda: Anda menjalankan exec pada kode python yang disimpan sebagai string.

Saat Anda menjalankan ini, akan dibuat payload.pkl dan mencetak berikut:

you've been pwned !
my friend needs to know this

Jika kita memeriksa isi file pickle, kita mendapatkan:

# cat payload.pkl
c__builtin__
exec
(Vprint("you've been pwned !")
tR my friend needs to know this.%

# hexyl payload.pkl
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚00000000β”‚ 63 5f 5f 62 75 69 6c 74 β”Š 69 6e 5f 5f 0a 65 78 65 β”‚c__builtβ”Šin___exeβ”‚
β”‚00000010β”‚ 63 0a 28 56 70 72 69 6e β”Š 74 28 22 79 6f 75 27 76 β”‚c_(Vprinβ”Št("you'vβ”‚
β”‚00000020β”‚ 65 20 62 65 65 6e 20 70 β”Š 77 6e 65 64 20 21 22 29 β”‚e been pβ”Šwned !")β”‚
β”‚00000030β”‚ 0a 74 52 80 04 95 20 00 β”Š 00 00 00 00 00 00 8c 1c β”‚_tRΓ—β€’Γ— 0β”Š000000Γ—β€’β”‚
β”‚00000040β”‚ 6d 79 20 66 72 69 65 6e β”Š 64 20 6e 65 65 64 73 20 β”‚my frienβ”Šd needs β”‚
β”‚00000050β”‚ 74 6f 20 6b 6e 6f 77 20 β”Š 74 68 69 73 94 2e       β”‚to know β”ŠthisΓ—.  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Pada dasarnya, inilah yang terjadi saat Anda melakukan unpickle:

# ...
opcodes_stack = [exec_func, "malicious argument", "REDUCE"]
opcode = stack.pop()
if opcode == "REDUCE":
    arg = opcodes_stack.pop()
    callable = opcodes_stack.pop()
    opcodes_stack.append(callable(arg))
# ...

Instruksi yang menimbulkan ancaman adalah STACK_GLOBAL, GLOBAL, dan REDUCE.

REDUCE adalah instruksi yang memberitahu unpickler untuk mengeksekusi fungsi dengan argumen yang diberikan, dan instruksi *GLOBAL memberitahu unpickler untuk import sesuatu.

Sebagai kesimpulan, pickle berbahaya karena:

  • saat mengimpor modul python, kode arbitrer dapat dieksekusi
  • Anda dapat mengimpor fungsi bawaan seperti eval atau exec, yang dapat digunakan untuk mengeksekusi kode arbitrer
  • saat menginstansiasi sebuah objek, konstruktor dapat dipanggil

Inilah mengapa sebagian besar dokumentasi yang menggunakan pickle menyatakan untuk tidak melakukan unpickle data dari sumber yang tidak tepercaya.

Strategi Mitigasi

Jangan gunakan pickle

Saran yang bagus, Luc, tetapi pickle digunakan secara luas dan tidak akan segera ditinggalkan: menemukan format baru yang memuaskan semua pihak dan memulai perubahan tersebut akan memakan waktu.

Jadi, apa yang bisa kita lakukan untuk saat ini?

Muat file dari pengguna dan organisasi yang Anda percaya

Di Hub, Anda memiliki kemampuan untuk menandatangani commit Anda dengan kunci GPG. Hal ini tidak menjamin bahwa file Anda aman, tetapi menjamin asal file tersebut.

Jika Anda mengenal dan mempercayai pengguna A, serta commit yang berisi file di Hub ditandatangani oleh kunci GPG pengguna A, maka cukup aman untuk berasumsi bahwa Anda dapat mempercayai file tersebut.

Muat bobot model dari TF atau Flax

Checkpoint TensorFlow dan Flax tidak terpengaruh, dan dapat dimuat dalam arsitektur PyTorch menggunakan kwarg from_tf dan from_flax untuk metode from_pretrained untuk menghindari masalah ini.

E.g.:

from transformers import AutoModel

model = AutoModel.from_pretrained("google-bert/bert-base-cased", from_flax=True)

Gunakan format serialisasi Anda sendiri

Format terakhir ini, safetensors, adalah format serialisasi sederhana yang sedang kami kerjakan dan uji coba saat ini! Tolong bantu atau berkontribusi jika Anda bisa πŸ”₯.

Perbaiki torch.load/save

Saat ini sedang berlangsung diskusi terbuka di PyTorch mengenai Cara aman untuk memuat hanya bobot dari file *.pt secara default – silakan ikut berkontribusi di sana!

Security Scanner Hub

Yang kita miliki saat ini

Kami telah membuat pemindai keamanan yang memindai setiap file yang diunggah ke Hub dan menjalankan pemeriksaan keamanan. Pada saat penulisan ini, pemindai tersebut menjalankan dua jenis pemindaian:

  • Pemindaian ClamAV
  • Pindai Impor Pickle

Untuk pemindaian ClamAV, file diproses melalui antivirus open-source ClamAV. Meskipun ini mencakup banyak file berbahaya, metode ini tidak mencakup eksploit pickle.

Kami telah mengimplementasikan pemindaian Pickle Import, yang mengekstrak daftar impor yang dirujuk dalam file pickle. Setiap kali Anda mengunggah pytorch_model.bin atau file pickled lainnya, pemindaian ini akan dijalankan.

Di hub, daftar impor akan ditampilkan di samping setiap file yang berisi impor. Jika ada impor yang terlihat mencurigakan, impor tersebut akan disorot.

Kami mendapatkan data ini berkat pickletools.genops yang memungkinkan kami membaca file tanpa mengeksekusi kode yang berpotensi berbahaya.

Perhatikan bahwa hal ini memungkinkan untuk mengetahui apakah, saat melakukan unpickling pada sebuah file, akan terjadi REDUCE pada fungsi yang berpotensi berbahaya yang diimpor oleh *GLOBAL.

Penafian: ini tidak 100% bebas dari kesalahan. Tanggung jawab Anda sebagai pengguna untuk memeriksa apakah sesuatu aman atau tidak. Kami tidak secara aktif mengaudit paket python untuk keamanan, daftar impor aman/tidak aman yang kami miliki dipelihara dengan upaya terbaik. Silakan hubungi kami jika Anda merasa sesuatu tidak aman, dan kami menandainya sebagai demikian, dengan mengirimkan email ke website di huggingface.co

Solusi potensial

Kita bisa memikirkan pembuatan Unpickler kustom seperti ini. Namun, seperti yang dapat kita lihat dalam eksploit canggih ini, hal itu tidak akan berhasil.

Untungnya, selalu ada jejak dari impor eval, sehingga membaca opcode secara langsung seharusnya memungkinkan untuk menangkap penggunaan yang berbahaya.

Solusi yang saya usulkan saat ini adalah membuat file yang menyerupai .gitignore tetapi untuk impor.

File ini akan menjadi daftar putih impor yang akan menandai file pytorch_model.bin sebagai berbahaya jika terdapat impor yang tidak termasuk dalam daftar putih.

Kita bisa membayangkan adanya format mirip regex di mana, misalnya, semua submodul numpy dapat diizinkan melalui baris sederhana seperti: numpy.*.

Bacaan Lanjutan

pickle - serialisasi objek Python - dokumentasi Python 3.10.6

Pickle Berbahaya - Serialisasi Python yang Merusak

GitHub - trailofbits/fickling: Dekompiler dan analis statis untuk pickling Python

Exploitasi Python pickles

cpython/pickletools.py di 3.10 Β· python/cpython

cpython/pickle.py di 3.10 Β· python/cpython

CrypTen/serial.py di main Β· facebookresearch/CrypTen

CTFtime.org / Balsn CTF 2019 / pyshv1 / Writeup

Memulihkan modul pickle Python

Update on GitHub