A backdoor in a LinkedIn job offer
A backdoor in a LinkedIn job offer
LinkedIn 工作邀请中的后门
Last week, I got a LinkedIn message from a recruiter at a small crypto startup. We exchanged a few messages over a couple of days, she described a broken proof-of-concept they needed a lead engineer for, and then sent me a public GitHub repo to review. Specifically, she asked me to “check out the deprecated Node modules issue.”
上周,我收到了一位小型加密货币初创公司招聘人员发来的 LinkedIn 消息。我们在几天内交换了几条信息,她描述了一个需要首席工程师来修复的概念验证(PoC)项目,并发送了一个公开的 GitHub 仓库让我审查。具体来说,她要求我“检查一下已弃用的 Node 模块问题”。
It’s not uncommon to ask for a review of an existing codebase, but something felt off and raised an alarm in my head, so I decided to get a bit extra paranoid. Instead of cloning and installing dependencies, I spun up a throwaway VPS on Hetzner, cloned the repo there, and pointed Pi at it in read-only mode, with only file-reading tools enabled: pi --tools read,grep,find,ls. I asked the agent to review the codebase and flag anything suspicious. It stopped almost immediately at app/test/index.js.
要求审查现有代码库并不罕见,但总觉得哪里不对劲,这引起了我的警觉,所以我决定多留个心眼。我没有直接克隆并安装依赖项,而是在 Hetzner 上启动了一个一次性的 VPS,将仓库克隆到那里,并以只读模式运行 Pi(AI 代理),仅启用文件读取工具:pi --tools read,grep,find,ls。我让代理审查代码库并标记任何可疑之处。它几乎立即在 app/test/index.js 处停了下来。
The backdoor
后门
The repo felt like a React frontend with a Node backend. The trap was in app/test/index.js, about 250 lines disguised as a test suite. Inside, a URL is assembled from fragments: const protocol = "https", domain = "store", separator = "://", path = "/icons/", token = "77", subdomain = "rest-icon-handler", bearrtoken = "logo"; These combine into https://rest-icon-handler.store/icons/77. Then, buried between walls of commented-out tests, the payload runs anything the server sends back to your machine.
这个仓库看起来像是一个带有 Node 后端的 React 前端项目。陷阱就在 app/test/index.js 中,大约 250 行代码伪装成测试套件。在内部,一个 URL 由多个片段拼接而成:const protocol = "https", domain = "store", separator = "://", path = "/icons/", token = "77", subdomain = "rest-icon-handler", bearrtoken = "logo"; 这些片段组合成了 https://rest-icon-handler.store/icons/77。随后,在大量被注释掉的测试代码掩盖下,有效载荷(payload)会执行服务器发送回你机器的任何指令。
How it triggers
触发方式
The file doesn’t wait for the tests to run. app/index.js itself executes const test = require('./test'), which loads and runs app/test/index.js. package.json wires app/index.js into startup: prepare runs app:pre, which is node app/index.js. The prepare script is the important one. npm runs prepare automatically after npm install, so just installing dependencies executes the backdoor. The instruction to “check out the deprecated Node modules issue” was bait to get me to run npm install.
该文件并不等待测试运行。app/index.js 本身执行了 const test = require('./test'),这会加载并运行 app/test/index.js。package.json 将 app/index.js 关联到启动项中:prepare 运行 app:pre,即 node app/index.js。prepare 脚本是关键所在。npm 会在 npm install 后自动运行 prepare,因此仅仅是安装依赖项就会触发后门。“检查已弃用的 Node 模块问题”这一指令,就是诱导我运行 npm install 的诱饵。
A borrowed identity
被盗用的身份
The commits in the repo were authored under the name and email of a real developer, a full-stack engineer with an ordinary LinkedIn profile, a personal website, and a GitHub account with a long history. I messaged him, pretending I’d inherited the codebase and had a few implementation questions, to see how he’d react. He told me he’d never worked for them. He’d been impersonated on GitHub before and had a repo taken down over it, and he had nothing to do with this one.
仓库中的提交记录使用的是一位真实开发者的姓名和邮箱,他是一位全栈工程师,拥有正常的 LinkedIn 个人资料、个人网站以及长期活跃的 GitHub 账号。我给他发了消息,假装我接手了代码库并有一些实现上的问题,想看看他的反应。他告诉我他从未为那家公司工作过。他之前在 GitHub 上就被冒充过,甚至导致一个仓库被下架,而这个仓库与他毫无关系。
A second borrowed identity
第二个被盗用的身份
The recruiter’s profile belonged to a real arts journalist, a well-known one I looked up later, with a long cultural background and nothing technical on it. When I played along and told her I couldn’t get the project to install, the journalist instantly turned into an expert on npm and Node versions. It was quite amusing, I’d say.
招聘人员的个人资料属于一位真实的艺术记者,我后来查了一下,她很有名,有着深厚的文化背景,没有任何技术相关经历。当我配合她并告诉她我无法安装该项目时,这位记者瞬间变成了 npm 和 Node 版本方面的专家。我不得不说,这相当滑稽。
This can happen to anyone
这种情况可能发生在任何人身上
I’ve heard of these attacks and read about them on HN, but when one came after me it still caught me a bit off guard. I suspected something from the first few messages, but on a more tired or rushed day, I could easily have run npm install before thinking it through. So, if you get a LinkedIn message asking you to review a repo, a bit of paranoia and good security hygiene never hurts.
我听说过这些攻击,也在 Hacker News 上读到过,但当它真的找上我时,还是让我有些措手不及。我从最初的几条信息中就产生了一些怀疑,但如果是在更疲惫或匆忙的日子里,我很容易在没深思熟虑的情况下就运行了 npm install。所以,如果你收到 LinkedIn 消息要求你审查代码库,保持一点偏执和良好的安全习惯永远不会错。