JFrog 和 Docker 在近期发现Docker Hub 存储库被用于传播恶意软件和网络钓鱼诈骗后,联手采取缓解和清理措施。
作者:安全研究员AndreyPolkovnichenko | 恶意软件研究团队负责人BrianMoussalli | 安全研究高级总监ShacharMenashe
作为软件生态系统的重要组成部分兼合作伙伴,JFrog与Docker正在携手优化软件生态系统。JFrog安全研究团队的工作包括通过持续监控开源软件注册表,主动识别和定位潜在的恶意软件与漏洞威胁。
通过持续扫描所有主要公共存储库,JFrog在NPM、PyPI和NuGet注册表中发现数个恶意软件包。在本文中,我们将介绍最近发现的针对Docker Hub 的三个大规模恶意软件攻击活动,这些活动植入了数百万个携带恶意元数据的“无镜像”存储库。研究发现,这些存储库包含的并非容器镜像(因此无法在 Docker引擎或Kubernetes集群中运行),而是恶意元数据。
Docker Hub是一个为开发者提供多样化功能的平台,它为Docker 镜像的开发、协作和分发开辟了许多可能性。目前,它是全球开发者首选的头号容器平台,托管着超过1500万个存储库。
然而,这些公共存储库的内容却出现了一个重大问题。JFrog研究显示,这些公共存储库中有近 20%(近300万个存储库!)托管恶意内容,包括通过自动生成的账户上传的用于推广盗版内容的垃圾邮件,以及恶意软件和钓鱼网站等极度恶意的实体。
虽然Docker Hub维护者目前对许多上传的存储库进行了管理,但这些攻击表明,要想完全阻止恶意上传的难度巨大。
是什么导致了这次攻击?
Docker Hub是Docker用于托管和分发镜像的云端注册服务。其核心概念是一个存储库,其中包含建立在容器数据基础上的文本描述和元数据。
Docker Hub 的存储库
虽然Docker存储库的核心功能是保存Docker 镜像的集合(一种可以通过固定名称更新和访问的应用程序),但Docker Hub引入了几项关键的增强功能,其中最重要的是社区功能。
对于公共存储库来说,Docker Hub 充当着一个社区平台的角色。用户可以通过它搜索和发现可能对其项目有用的镜像,可以对存储库进行评分和评论,帮助他人衡量可用镜像的可靠性和实用性。
为帮助用户搜索和使用镜像,Docker Hub允许存储库维护者添加HTML格式的简短描述和文档,这些描述和文档将显示在库的主页上。库文档通常用于解释镜像的用途并提供使用指南。
合法存储库文档示例
但如果以墨菲定律的角度来看,在网络安全领域,如果恶意软件开发者可以利用某些漏洞,那么它将无法避免这一隐患。
JFrog 的安全研究团队发现,Docker Hub中约有460万个存储库是没有镜像的,除了存储库的文档外没有任何内容。深入检查后发现,这些被上传的无镜像存储库,绝大多数都是带着恶意目的——它们的概述页面试图欺骗用户访问钓鱼网站或托管着危险恶意软件的网站。
在讨论各种恶意载荷之前,我们将先一步说明发现这些恶意存储库的方法。
识别恶意存储库
JFrog首先从 Docker Hub存储库的发布模式中发现了异常。为此,我们调出了过去五年中发布的所有“无镜像”Docker Hub存储库,按创建日期进行分组,并将其绘制成图表:
每月存储库创建图
正如所见,Docker Hub上的活动轨迹通常比较平稳,但2021年和2023年出现了几个峰值。如果放大观察,就会发现每天的活动都很明确并且与常规工作日时间吻合。甚至在视觉上,就能注意到工作日创建的存储库较多,而周末创建的存储库较少。
放大观察2023年的异常情况
从图中可以看出,当异常活动开始时,每天创建的存储库数量会翻十倍
通过深入分析在异常日创建的存储库,发现许多存储库偏离常规。主要的偏差在于它们不包含容器镜像,只包含一个文档页面,这就导致该库不能像正常的docker镜像那样被拉取和运行,从而无法使用。
恶意存储库示例
例如,上述截图中显示的存储库在描述中包含了几个链接,引导用户访问一个钓鱼网站。该网站欺骗毫无戒心的访问者,承诺为他们购买处方药,但随后却窃取他们的信用卡信息。
虽然所有异常存储库之间存在一定的差异,而且是由不同用户发布,但大多数都遵循相同的模式。这使我们能够创建一个签名,并按系列(或活动)对其进行分组。在对所有无镜像存储库应用这一签名后,我们采集了发布这些存储库的中心用户列表,将这些用户发布的所有存储库也归类为恶意软件。
在将这些活动绘制在时间轴上后,能够清晰地了解规模最大的恶意软件攻击活动的运作时期。
其中两个活动在2021年上半年最为活跃,每天发布数千个存储库。下载器活动在2023年8月又进行了一次尝试。网站活动的运作方式与之不同,三年来始终坚持每天推送少量存储库。
每天注册的恶意存储库(按活动)
在DockerCon 2023 召开时,Docker Hub已包含1500万个存储库,因此我们将使用这个数字作为Docker Hub存储库的基准总数。
发布到 Docker Hub 的无镜像存储库总数为460万,占所有公共存储库的30%。我们能够将其中的281万个(约占19%)存储库与这些大型恶意攻击活动联系起来。
除了已发现的大型活动外,在分析中还发现了一些较小的存储库。这些活动似乎主要集中在垃圾邮件/搜索引擎优化方面,暂时无法对这些活动的所有变化形态进行分类。这些较小的“活动”每个包含不到1000个软件包。在分类时,我们将这些较小的数据集归入了一个标有“其他可疑”的组别。
DockerHub存储库分类
恶意软件镜像分布(按活动):
由此可以总结出恶意软件镜像有不同的传播方式。“下载器”和“电子书钓鱼”活动会在短时间内分批创建虚假存储库,而“网站”活动则会在整个时间段内每天创建多个存储库,并在每个存储库中使用单个用户。
现在,通过了解在 Docker Hub 上运行的主要恶意软件活动,接下来将深入回顾一下它们的策略和手段。
1. “下载器”活动
2. “电子书钓鱼”活动
3. “网站SEO”活动
Docker Hub恶意软件活动分析
1. “下载器”活动
下载器活动存储库分布
属于该活动的存储库包含自动生成的文本,这些文本含有搜索引擎优化(SEO)文本,会建议下载盗版内容或视频游戏作弊器。此外,这些文本还包括所谓“广告软件”的链接。
该活动分两轮进行(约2021年和2023年),两轮都使用了完全相同的恶意载荷(见以下分析)。
带有恶意软件下载链接的恶意存储库示例
2021年活动——伪装成URL缩短器的恶意域
该活动中使用的大多数URL都假装使用已知的URL缩短器(例如 tinyurl.com),这与2021年发现的谷歌广告攻击活动类似。尝试解析后发现,不同于真正的缩短器,这些恶意缩短器实际上并不对URL进行编码。相反,它们会对文件名进行编码,并在每次关闭恶意资源时将链接解析到不同的域。
例如在我们调查期间,URL bitly[.]com/1w1w1被重定向到https[://]failhostingpolp[.]ru/9ebeb1ba574fb8e786200c62159e77d15UtXt7/x60VKb8hl1YelOv1c5X1c0BuVzmFZ8-teb-LRH8w。但该服务器每次收到后续请求,都会触发生成一个新的 URL路径。
其唯一目的就是充当恶意CDN的代理。
同一缩短链接每次收到后续请求都会带来不同的URL,并且如果托管恶意文件的服务器被关闭,缩短器将返回一个指向新活动服务器的链接。
我们收集了所有恶意域的列表,并编制了一个表格,显示欺诈性缩短器与其真实可信版本之间的对应关系。
恶意软件活动使用的伪造网址缩短器
该策略于2021年制定,在反病毒公司发现链接列表并将其添加到黑名单之前,它曾短暂的起过一段时间的作用。目前,当有人试图访问上表中的链接时,浏览器和服务提供商会发出警报。
2023年活动——加强型反检测技术
第二轮活动发生在2023年,主要侧重于避免被检测。恶意存储库不再直接链接到恶意资源,而是将合法资源重定向到恶意资源。
在这些资源中有一个托管在 blogger.com 上的页面,其中包含的JavaScript代码会在 500 毫秒后重定向到恶意载荷:
另一种方法是谷歌中一个著名的开放式重定向漏洞,恶意行为者可以使用特定参数将用户重定向到带有合法 Google 链接的恶意网站。
通常情况下,Google 链接 https://www[.]google[.]com/url?q=https%3A%2F%2Fexample.us%2F不会将用户重定向到目标网站。相反,它会显示一个警告,提示用户正被重定向到另一个域。
不过,可以通过添加非法参数 usd 来禁用此警告。该参数包含一个散列值或签名,可使 google.com 自动重定向到目标网站。
该重定向指向目标网站。在编撰本报告时,目标网站是gts794[.]com 和 failhostingpolp[.]ru 。这些网站诱使受害者下载广告软件。但无论登陆页面上的名称如何,下载的文件始终是同一个带有EXE安装程序的压缩包。正如我们在AnyRun分析中看到的,恶意软件会将名为 freehtmlvalidator.exe 的二进制文件安装到“%LOCALAPPDATA%\HTML Free Validator ”目录中。
下载器活动中的恶意载荷
“下载器”活动载荷分析
“下载器”活动的载荷是一个恶意可执行文件,大多数杀毒引擎将其检测为普通木马。
该恶意软件是用曾经风靡一时的Delphi环境的后继版本Embarcadero RAD Studio 27.0编写的:
该恶意软件使用HTTP POST请求与C2C服务器 http://soneservice[.]shop/new/net_api 通信。该请求是一条JSON消息,与三字节密钥“787”XOR并用十六进制编码。
{
"5E4B1B4F": "4D571F435C025E5B0A465B114B4602455C0F4B460A",
"465B0F": "664eed76ed570dbb4cba2bdcb3479b5f",
"4E531F4B":"en"
}
JSON 字段的编码方式相同,但使用了不同的密钥:“*2k”。了解到这一点后,我们就可以对以下请求进行解码
{
"type": "getinitializationdata",
"lid": "664eed76ed570dbb4cba2bdcb3479b5f",
"data":"en"