0x00 前言
前几天 Samba
公开了一个远程代码执行的漏洞,然后各种 POC 也随之出现, exploit-db
上也有这样一个 Python 版本的 POC: Samba 3.5.0 - Remote Code Execution.
直接执行 POC,报错误信息:
这种情况非常简单,直接 pip install smb
就行,但是:
好吧,我们还是需要 Google 一下这个 smb
的 package 名字,最后发现原来是 pysmb
:
最后 POC 终于跑了起来.
我们再回过头来看看整个流程,似乎并没有什么地方不对劲。
直接说问题所在吧,如果你在 2017-05-24
到 2017-05-31
这段时间内执行过 pip install smb
或者 pip download smb
, 那么恭喜你,你的名字可能出现在我的绵羊墙上。
0x01 试水 (2017-05-23 19:00)
第一天,我在 PyPI 上投放了 4 个 evil package: python-dev
、mongodb
、proxy
、shadowsock
测试一下不检查 package、随意安装 package 的人有多少。
其中所有的内容都是用 cookiecutter
根据模版 cookiecutter-evilpy-package 生成。
每个 package 都会收集用户的
- username
- hostname
- ip
- hostinfo
我选择了 GitHub Issues + webtask.io 的方式,将安装 evil package 的用户信息通过 webtask.io 中转到 GitHub Issues 上对外公开。
所以我就在 Github 上注册了个小马甲 evilpackage 专门提交 Issue。
因为 webtask.io 获取客户端 ip 的时候,其实获取到的是 webtask.io 前面 nginx 的 ip 地址,并不是用户的 ip,所以就只能在代码里面获取客户端的外网 ip. 使用 webtask.io 和 GitHub Issues 的主要原因是这两都是免费的。
0x02 增加投放 package (2017-05-24 19:00)
查看了一天的 Issues 数量,大概有 700+,效果非常不错,决定继续投放 evil package。 与此同时,@ztz 同学也加入了游戏,也在 RubyGems 上投放 Gems。
继续投放 evil package,就必须想一些比较好的名字,我主要使用下面两种方法:
Google 搜索提示框
直接根据 Google 的搜索框提示:
便收集到了没有在 PyPI 上注册,而且比较流行的 Package 名字:
- caffe
- ffmpeg
- git
- mkl
- opencl
- opencv
- openssl
- pygpu
- tkinter
- vtk
- proxy
想象力
依据平时写代码的经验总结出下面可能觉得会常用,但并没有在 PyPI 上注册的 Package 名字:
- ftp
- smb
- hbase
- samba
- rabbitmq
- zookeeper
- phantomjs
- memcached
- requirement.txt
- requirements.txt
其中 requirements.txt
并没有注册成功,稍后再说。
0x03 暂停服务 (2017-05-25 23:00)
晚上回家的时候又统计了一下安装量,一天安装量达到了 2000+,效果已经很显著,不必再增加新的 package 了,但是到了晚上 23:00 的时候, 我的 GitHub Issues 被恶意插入脏数据,所以只能暂停服务:
之所以只能暂停服务,那是因为 webtask.io 没法获取客户端 ip,我也没法 ban 掉对应的 ip,作出任何相对的处理,只能停服务。
话说到底谁才是攻击者。
0x04 evilpackage 被封 (2017-05-26 2:00)
我专门提交 Issue 的小马甲 evilpackage 因为触发了 GitHub 对 Spam 的检测,所以被封号了。 早上起床看到消息后,立马写邮件申诉,直到 2017-05-26 13:00 终于回复我的邮件了:
0x05 放弃 webtask.io (2017-05-26 19:00)
为了避免和之前一样被恶意插入脏数据,决定要放弃 webtask.io,每月花费 $10 巨款购入一台 vps。
使用 nginx + flask 的配置,继续将 user data 提交到 GitHub Issues 上。
nginx 的 ngx_http_limit_req_module
模块最大能够支持 1s/m
,也就是最多可以限制每个 ip 在每分钟内最多请求一次, 所以我们必须修改 ngx_http_limit_req_module
模块代码
增加一个 else if
block,直接将 scale 增加到 1000,这样就能限制每个 ip 在 16 min 内只能访问一次我们的接口, 除非使用大量代理,不然很难在短时间内插入大量脏数据。
0x06 repo 被封 (2017-05-27 3:00)
早上起床刷新一下 GitHub Issues 页面,结果发现:
邮件:
赶紧先上服务器加上一行代码,将用户上传的数据先暂时存在本地(之前太懒)。 然后马上回邮件,问情况,两天后:
解封无望,之前的数据大概就是没了。
目前还能通过 GitHub Search 找到以前的部分数据 GitHub Issue
0x07 写 web 界面 (2017-05-30 19:00):
由于之前一直在忙,最后拖到了30号才开始写 web 展示界面 http://evilpackage.fatezero.org/
也准备好新的 cookiecutter 模版 cookiecutter-evilpy-package
新的 cookiecutter 模版会提示用户安装了 evilpackage,并打开用户的浏览器去访问 http://evilpackage.fatezero.org/,让用户知道,自己已经是绵羊墙上的一员了。
计划打算第二天再往 PyPI 上提交新版本的 Package。
0x08 清空 (2017-05-31):
早上查找资料的时候发现,原来已经有好几批人干过和我一样类似的事情了
前两批都只是上传一个 package 用来提示安装用户,也防止恶意用户使用这些 package 名字, 后面一个小哥和我一样收集了用户不太敏感的信息,只不过他的数据一直没有公开。
过了一会 @ztz 同学告诉我他的 RubyGems 被清空了。
再过了一会我这边也被 PyPI 管理员警告要删除账号了,所以我就把所有的 Package 给删除了,账号也给删除了。
目前为止所有的 package 又回到了 unregister 的状态, 任何人都可以继续注册使用我之前注册的 package.
0x09 数据统计
目前我只能对在 http://evilpackage.fatezero.org/ 上那 10685 条数据进行统计
从 2017-05-27 10:38:03 到 2017-05-31 18:24:07,总计 106 个小时内, 有 9726 不重复的 ip 安装了 evil package,平均每个小时有 91 个 ip 安装了 evil package。
每个 package 命中排名:
前 50 个国家命中排名
每个访问排名
光从这几天来看,在 PyPI 上投放 evilpackage 的效果还是非常不错的, 每天都会有大概 2200+ 个独立 ip 进行访问,数据量稍微比之前那位小哥好一点, 也就是说,即便是类似的文章发出来,过了一年之后,随意安装 package 的这种情况完全没有改善,可能更严重了。
那位小哥释放掉所有的 package 之后,我作为一个 “恶意者” 再次使用他之前使用的 git
、openssl
名字来统计数据, 我作为一个 “恶意者”,被官方勒令删除所有的 package,这些 package 名字再次被释放,我比较好奇下一位 “恶意者” 会是谁, 会在 package 里放什么?会是和我一样收集数据,还是直接 rm -rf /
,还是勒索。拭目以待。
0x10 requirements.txt
一般经常使用 Python 的人都知道 requirements.txt
是整个项目的依赖文件,一般这样使用:
不过也有可能一时手速过快,敲打成
所以 requirements.txt
也是一个比较好的 evil package 的名字
诡异的 requirements.txt
在 2017-05-24 19:00 晚上,我尝试在 PyPI 注册上传 requirements.txt
的时候:
嗯,都失败了,但是 GitHub Issues 上竟然会有 153 个和 requirements.txt
相关的 Issues:
我并不怀疑这些 requirements.txt
数据的真实性,因为就没有人知道我尝试上传过 requirements.txt
,所以这些数据肯定是真实的。
PyPI 上也并不存在 requirements.txt
信息,本地尝试安装也失败了,至今仍未明白这种情况为何发生。
绕过 PyPI requirements.txt
的限制
在 PyPI 账号被删除之后,我还是对 requirements.txt
很好奇,为什么之前 GitHub 上会有记录? 能不能绕过 PyPI 的限制?下面简单讲一下如何绕过 PyPI 的限制。
我们直接查看提交 Package 时,PyPI 对 Package 名字限制的地方:
通过上面的代码,我们可以看到 PyPI 直接硬编码 'requirements.txt', 'rrequirements.txt', 'requirements-txt', 'rrequirements-txt'
禁止用户上传这些文件。
我们再看看 pip install xxx
的时候,PyPI 是怎么查找 Package 的:
好吧,直接查找数据库,我们再跟下来看 normalize_pep426_name
:
看到中间那个正则了吧,这也就意味着
这几条命令其实都是等价的,都是在安装 youtube_dl
, 那么我们就可以很容易的就绕过 PyPI 的限制, 直接上传一个 requiremnets--txt
:
来来来,我们直接尝试 pip install requirements.txt
:
通过上面的图,我们可以看到 PyPI 已经返回我们的 package url, 到了 pip 准备安装这个 package 的时候报错了,所以直接看 pip 代码:
看了代码,也就是没法在 url 中获取 package 的版本号, 因为 package 的名字(requirements--txt
)和搜索名字(requirements.txt
)对不上,我们得找找其他方法:
看到这里,大家应该也知道了,之前我们一直都是使用 source 的方式提交 package,如果我们直接打包成 wheel, 根据上面的代码,就不会再报错了,我们重新打包,再次上传:
终于成功了,当然 wheel 安装方式并不能直接执行命令, 不过我们可以通过给 requirements.txt
添加一个恶意依赖达到执行任意代码的效果。
在这里,我就添加了一个名为 ztz
的 source package,用于提醒安装 requirements.txt
的用户
0x11 总结
想做一件坏事情真不容易,快去看看 http://evilpackage.fatezero.org/ 上面有没有你的名字。
来源:fate0
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)