This week I learned about a design flaw with
pip download, which allows an adversary to run arbitrary code.
I assumed that running
pip install means anything could happen, but
pip download seems a bit surprising.
Both seem useful for red teaming though.
This post from Yehuda Gelb named Automatic Execution of Code Upon Package Download on Python Package Manager which the Security Now! podcast pointed me towards.
The post highlights that just running
pip download can compromise your computer.
It turns out that this behavior is known since at least 2014, based on a pip Github issue named Avoid generating metadata in pip download which raised this concern.
Let’s investigate this more closely.
Authoring a Python package
I had never built a python package before, but the process is well documented. There are different ways to go about it, and the vulnerability exists when using the older
setup.py variation and when the package is in a
There is a basic project I put together on Github:
git clone https://github.com/wunderwuzzi23/this_is_fine_wuzzi
The key piece, which took a few minutes to figure out (stackoverflow is your friend), was to figure out how to specify a command to run.
The way I went about it (not sure if there are others), is to include
cmdclass in the setup, which causes pip to execute the provided command function upon both
install of the package.
'install' : RunInstallCommand,
My demo just runs a
Now, we are good to go.
Building the package
The next step is to bundle it up in a package, which is done using:
python -m build
This will create a
./dist/ folder containing the
For this demo exploit we are only interesting in the
In case you encounter errors building, make sure to have
build packages installed.
Hosting the package using pypi-server
Now it’s time to host the package on a server.
To test this out, I decided to host the package on my own test
pypi-server run -v -p 8080 ./packages
Then copy the
tar.gz file to your pypi-server’s
Above screenshot shows how this looks once running.
Download or Install the package
--index-url it’s possible to point
pip to an alternate package server, which is what we can leverage to test this out:
pip download this_is_fine_wuzzi --index-url http://amstrad:8080 --trusted-host amstrad -v
And our code will run now as part of this.
- Messages printed to console won’t be shown unless you specify
--trusted-hostoption was added because this was quick demo test. If you have a TLS connection with a valid certificate this will not be needed.
There are now tons of ways this can be abused, e.g. just thinking of Jupyter notebooks or Google Colab, etc.
setup.py is only executed if the package is in
tar.gz format. So, either reviewing the tar file or making sure there is a wheel file (
.whl) present and used.
You can enumerate the offered packages via
This way one can see what files are hosted for the package (tar or wheel, or both), and download (e.g.
wget) and inspect the
tar.gz file if that is the only option.
pip download execute arbitrary code is quite unexpected and easy to repro and perform for an attacker.
Make sure to inspect packages before you download or install them using