Hub 文件

Pickle 掃描

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

Pickle 掃描

Pickle 是機器學習中廣泛使用的序列化格式。最值得注意的是,它是 PyTorch 模型權重的預設格式。

當你載入一個 pickle 檔案時,可能會遭受危險的任意程式碼執行攻擊。我們建議從你信任的使用者和組織載入模型,依賴經過簽名的提交,和/或使用 from_tf=True 自動轉換機制從 TF 或 Jax 格式載入模型。我們還透過直接在 Hub 上顯示/“審查”任何 pickle 檔案中的匯入列表來緩解此問題。最後,我們正在試驗一種名為 safetensors 的新的、簡單的權重序列化格式。

什麼是 pickle?

來自官方文件

pickle 模組實現了用於序列化和反序列化 Python 物件結構的二進位制協議。

這意味著 pickle 是一種序列化協議,用於在各方之間高效地共享資料。

我們稱 pickling 過程中生成的二進位制檔案為 pickle。

pickle 的核心基本上是一堆指令或操作碼。正如你可能猜到的,它不是人類可讀的。操作碼在 pickling 時生成,並在 unpickling 時按順序讀取。根據操作碼,執行給定的操作。

這裡有一個小例子:

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)

當你執行這個指令碼時,它會建立一個 pickle 檔案並在你的終端列印以下指令:

    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

現在不用太擔心這些指令,只需知道 pickletools 模組對於分析 pickle 非常有用。它允許你讀取檔案中的指令,而無需執行任何程式碼。

Pickle 不僅僅是一個序列化協議,它透過賦予使用者在反序列化時執行 python 程式碼的能力,提供了更大的靈活性。聽起來不太妙,對吧?

為什麼它很危險?

正如我們上面所述,反序列化 pickle 意味著可以執行程式碼。但這有一些限制:你只能引用頂層模組中的函式和類;你不能將它們嵌入到 pickle 檔案本身中。

回到畫板上

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)

當我們執行這個指令碼時,我們再次得到了 `payload.pkl`。當我們檢查檔案內容時:


# 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.  │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘

我們可以看到裡面沒有太多東西,只有幾個操作碼和相關資料。你可能會想,那麼 pickle 有什麼問題呢?

讓我們嘗試些別的

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)

這裡為了簡單起見,我們使用了 fickling 庫。它允許我們新增 pickle 指令,透過 `exec` 函式執行包含在字串中的程式碼。這就是你繞過 pickle 中不能定義函式或類的限制的方法:對儲存為字串的 python 程式碼執行 exec。

當你執行這個指令碼時,它會建立一個 `payload.pkl` 並列印以下內容:

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

如果我們檢查 pickle 檔案的內容,我們會得到:

# 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×.  │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘

基本上,這就是 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))
# ...

構成威脅的指令是 STACK_GLOBALGLOBALREDUCE

REDUCE 告訴 unpickler 用提供的引數執行函式,而 *GLOBAL 指令告訴 unpickler 去 `import` 東西。

總而言之,pickle 是危險的,因為:

  • 當匯入一個 python 模組時,可以執行任意程式碼
  • 你可以匯入像 `eval` 或 `exec` 這樣的內建函式,它們可以用來執行任意程式碼
  • 當例項化一個物件時,建構函式可能會被呼叫

這就是為什麼大多數使用 pickle 的文件中都說明,不要 unpickle 來自不受信任來源的資料。

緩解策略

不要使用 pickle

聽起來是個好建議,Luc,但 pickle 被大量使用,並且短期內不會消失:找到一個讓所有人都滿意的新格式併發起變革需要一些時間。

那麼我們現在能做些什麼呢?

從你信任的使用者和組織載入檔案

在 Hub 上,你可以使用 GPG 金鑰簽署你的提交。這並不能保證你的檔案是安全的,但它確實保證了檔案的來源。

如果你瞭解並信任使用者 A,並且 Hub 上包含該檔案的提交是由使用者 A 的 GPG 金鑰簽署的,那麼可以相當安全地假設你可以信任該檔案。

從 TF 或 Flax 載入模型權重

TensorFlow 和 Flax 的檢查點不受影響,並且可以在 PyTorch 架構中透過 `from_pretrained` 方法的 `from_tf` 和 `from_flax` 關鍵字引數載入,以規避此問題。

例如

from transformers import AutoModel

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

使用你自己的序列化格式

最後這個格式 `safetensors` 是我們目前正在開發和試驗的一種簡單序列化格式!如果可以的話,請幫忙或貢獻程式碼 🔥。

改進 torch.load/save

在 PyTorch 上有一個正在進行的公開討論,關於預設情況下以安全方式僅從 *.pt 檔案載入權重——請加入討論!

Hub 的安全掃描器

我們現在有什麼

我們建立了一個安全掃描器,可以掃描推送到 Hub 的每個檔案並執行安全檢查。在撰寫本文時,它執行兩種型別的掃描:

  • ClamAV 掃描
  • Pickle 匯入掃描

對於 ClamAV 掃描,檔案會透過開源防毒軟體 ClamAV 進行掃描。雖然這涵蓋了大量危險檔案,但它不包括 pickle 漏洞。

我們實現了一個 Pickle 匯入掃描,它會提取 pickle 檔案中引用的匯入列表。每次你上傳 `pytorch_model.bin` 或任何其他 pickle 檔案時,都會執行此掃描。

在 Hub 上,匯入列表將顯示在每個包含匯入的檔案旁邊。如果任何匯入看起來可疑,它將被高亮顯示。

我們透過 pickletools.genops 獲得這些資料,它允許我們讀取檔案內容而無需執行潛在的危險程式碼。

請注意,這使得我們能夠知道,當 unpickling 檔案時,它是否會對一個透過 `*GLOBAL` 匯入的潛在危險函式執行 `REDUCE` 操作。

免責宣告:這並非 100% 萬無一失。作為使用者,你有責任檢查某些內容是否安全。我們不會主動審計 python 包的安全性,我們的安全/不安全匯入列表是以盡力而為的方式維護的。如果你認為某些東西不安全,而我們將其標記為安全,請透過傳送電子郵件至 website at huggingface.co 與我們聯絡。

潛在解決方案

有人可能會想到建立一個自定義的 Unpickler,類似於這個。但正如我們在這個複雜的漏洞利用中看到的,這行不通。

幸運的是,`eval` 匯入總會留下痕跡,所以直接讀取操作碼應該能捕捉到惡意使用。

我目前提出的解決方案是建立一個類似於 `.gitignore` 的檔案,但用於匯入。

這個檔案將是一個匯入的白名單,如果 `pytorch_model.bin` 檔案中有未包含在白名單中的匯入,該檔案將被標記為危險。

可以想象使用一種類似正則表示式的格式,例如透過一行簡單的 `numpy.*` 來允許所有的 numpy 子模組。

進一步閱讀

pickle - Python object serialization - Python 3.10.6 documentation

Dangerous Pickles - Malicious Python Serialization

GitHub - trailofbits/fickling: A Python pickling decompiler and static analyzer

Exploiting Python pickles

cpython/pickletools.py at 3.10 · python/cpython

cpython/pickle.py at 3.10 · python/cpython

CrypTen/serial.py at main · facebookresearch/CrypTen

CTFtime.org / Balsn CTF 2019 / pyshv1 / Writeup

Rehabilitating Python’s pickle module

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.