Nook in the Lunar Mare

PyPi的Typosquatting Attack

Jan 1, 2019

起因

今天对接一个项目时需要用到tensorflow等库,于是创建了一个虚拟环境后在里面直接pip安装。

pip install tensoflow

等到下载结束后发觉不对劲,原来我打错了,心中一惊,因为最近正好有一个利用用户拼写错误的pip攻击事件闹得沸沸扬扬,主角是request包(正确包名应该是requests,之前也经常敲错,当时还没有被污染)不过没太在意,过了大约几十分钟在/home目录下才注意到多出了一个txt文件名为typosquating-attack,里面的内容是

You have been hacked since 2020-08-11 11:05:20.548690

emmmmmm,我中招了。

回溯

好吧,首先确定的是这个攻击者大概率是个善意攻击者,否则就不会这么好心留下这个字条了。检查日志发现没有什么恶意的操作,这个东西可能就是个概念验证的产物,可以一步步回去看它是怎么完成这一系列操作的。

尽管十分怀疑是pip的问题,还是需要先定位到我在11:05分时运行了哪个命令。

# 如果你使用的是bash
# if you are using bash as your defaut shell
export HISTTIMEFORMAT='%F %T'
history

# 如果用的是zsh
# or if you are using zsh
history -i  # or history -E

最终定位到如下几条命令

 6726  11.8.2020 11:05  ls
 6727  11.8.2020 11:05  python detection.py
 6728  11.8.2020 11:05  pip install tensoflow
 6729  11.8.2020 11:05  pip install tensorflow

确认其是来自tensoflow的攻击,接下来来到之前的虚拟环境文件夹venv中,在site-packages里仅找到tensoflow-distinfo,里面的METADATA显示它的确是一个typosquatting_attack的概念代码,还有github链接,可以直接去上面看代码了。

如果没有找到这类信息的话,我会去对应源的index中寻找,以tensoflow为例,我去的就是https://pypi.tuna.tsinghua.edu.cn/simple/,找到tensoflow的包,把对应的tar.gz下载下来进行分析。

代码

解压tar.gz包后目录如下,重点只关注setup的代码

├─PKG-INFO
├─README.md
├─setup.cfg
├─setup.py
└─tensoflow.egg-info/
  ├─dependency_links.txt
  ├─PKG-INFO
  ├─SOURCES.txt
  └─top_level.txt

setup.py代码如下:

#!/usr/bin/env python

from __future__ import print_function

import getpass
import os
import time
import datetime
import ctypes
import sys
import platform

from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install

long_description_filename = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), 'README.md')

with open(long_description_filename) as fd:
    long_description = fd.read()

FILENAME = 'typosquating-attack'
USER_PATH = os.path.join(os.path.expanduser('~'), FILENAME)
TIME = str(datetime.datetime.now())

def touch_file():
    with open(USER_PATH, 'a') as user_fd:
        message = 'You have been hacked since {}'.format(TIME)
        print(message)
        user_fd.write(message + '\n')

class PostDevelopCommand(develop):
    def run(self):
        touch_file()
        develop.run(self)


class PostInstallCommand(install):
    def run(self):
        touch_file()
        install.run(self)

setup(
    name='tensoflow',
    version='0.0.5',
    description='A typosquatting attack under package name tensoflow.',
    long_description=long_description,
    long_description_content_type='text/markdown',
    url='https://github.com/amboulouma/tensoflow',
    packages=[],
    license='MIT',
    classifiers=[
        'Environment :: Console',
        'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
        'Operating System :: MacOS :: MacOS X',
        'Operating System :: Microsoft :: Windows',
        'Operating System :: POSIX',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Topic :: Security',
    ],
    install_requires=[],
    tests_require=[],
    cmdclass={
        'develop': PostDevelopCommand,
        'install': PostInstallCommand,
    },
    include_package_data=True,
)

检查下来,三个函数都没有恶意行为,仅仅是生成了一个文本文件,整个过程就是pip下载,利用user的权限运行该setup脚本,在user的home目录中创建了文本文件。postdevelop应该是作者测试时使用,install内的内容才是安装时的行为。

后记

pip的第三方库行为依赖于第三方库实现者,官方对此没有进行校验和审核,除了开源社区对代码的检视以外(而且一是代码贡献者并不多,二是代码贡献者也不一定会完整地阅读整个项目源码,这也导致理解代码并有足够水平发现后门的可能性降低,大项目可能情况好些,对于小项目参与代码审计的可能就很少了),其实没有什么更强的约束力,所以恶意攻击者完全可以在代码中埋后门。

这个攻击之前javascript的npm仓库也发生过,而且pip的投毒感觉是一个几年前的老技术了,不知道为何最近又被提起。可能是最近的request攻击实实在在地搭载了恶意代码进行挖矿吧。还好这次的tensoflow是一个19年的概念验证,而非真正的恶意程序。