PyFu

Python Magic Methods and Attributes

Core Python Concepts

Python magic methods, also called dunder methods which is short for double underscore, are special functions that allow developers to define how objects behave under various operations.

Their names start and end with double underscores, such as __init__, __str__, or __getitem__.

Unlike regular methods, these are rarely called directly. Instead, Python invokes them automatically when certain operations are performed on the object.

For example, when an object is instantiated, Python internally calls its __init__ method. When the object is printed, __str__ or __repr__ may be called to obtain its string representation.

This system allows developers to tightly integrate custom objects into Python’s syntax and behavior.

Magic attributes serve a similar purpose but provide metadata rather than behavior. These attributes, also prefixed and suffixed with double underscores, give introspective information about objects or classes. Examples include __class__ for the object’s class, __dict__ for the instance namespace, and __mro__ for method resolution order.

By using magic methods and attributes, Python allows a high degree of customization and syntactic flexibility, enabling developers to implement advanced object-oriented patterns, operator overloading, and protocol compliance.

__import__ Magic Function

The __import__ function is a built-in low-level hook used by Python’s import system to import modules. Normally, developers use the import statement, but behind the scenes, Python may invoke __import__ to perform the actual import logic.

Since __import__ dynamically loads modules by name, it can be used to access modules like os at runtime. Once imported, any function from that module can be called, including dangerous ones like os.system, which allows execution of system commands.

For example:

>>> __import__("os").system("whoami")
hacker
0

Why Understanding Magic Functions is Important

Mastering Python’s magic methods allows developers to deeply customize how objects behave under various operations. By controlling these methods, it’s possible to define how objects respond to instantiation, representation, arithmetic, comparison, iteration, attribute access, context management, and more.

For offensive security, understanding magic functions is essential because many serialization formats, template engines, and code execution vulnerabilities involve objects being implicitly processed by Python’s internals.

Magic methods such as __getattr__, __reduce__, __setstate__, or __import__ often play a role in exploitation chains, especially in deserialization attacks or dynamic code execution scenarios.

Why magic methods matter from an offensive security perspective

I treat dunder methods and attributes as the seams of the object model, the points where Python hands control back to user-defined code without anyone calling a function by name. That implicit invocation is exactly what makes them an attack surface. When a library deserializes an object, renders a template, formats a string, or hashes a value, it is silently triggering magic methods, and any one of them can be the place where attacker-controlled behavior executes. I do not need to find a call to my payload; I need to find a magic method the framework will call on my behalf.

The two halves matter for different reasons. Magic methods (__reduce__, __setstate__, __getattr__, __call__, __init_subclass__) are behavior hooks, the rungs that turn a benign-looking operation into code execution. Magic attributes (__class__, __dict__, __mro__, __globals__, __builtins__, __subclasses__) are the introspection ladder that walks from any object to the full loaded class graph and the builtins table. Together they cover both “make it run my code” and “reach the code I want.”

When auditing for magic-method abuse, this is what I look for:

  • __reduce__, __reduce_ex__, __setstate__, __getstate__ on anything deserialized. These are the pickle and copy entry points; an attacker-defined __reduce__ returns a callable plus arguments that the unpickler will execute. See Insecure Deserialization - Python Pickle.
  • __getattr__ and __getattribute__ on proxies, ORMs, and config objects. Dynamic attribute resolution can be coaxed into resolving attacker-chosen names, which is the core of SSTI attribute walking.
  • __class__ / __base__ / __mro__ / __subclasses__ reachable in a sandbox. The climb to object and out to Popen lives entirely in these attributes. See Walking the Python Object Graph with subclasses().
  • __globals__, __builtins__, __import__ reachable from any function. This is the pivot from a class reference to __import__('os').system(...).
  • __init_subclass__, __set_name__, __call__, __format__ as less obvious hooks that fire during class creation, descriptor binding, or string formatting.

For defenders the takeaway is that any feature which implicitly invokes a magic method on attacker-supplied data is a code-execution sink until proven otherwise.