Apifox CDN Supply Chain Attack: A Detailed Technical Walkthrough
On March 25, 2026 a malicious script hijacked Apifox's CDN, inflating a 34 KB tracking file to 77 KB and using obfuscated JavaScript, RSA and AES‑256‑GCM encryption to collect system fingerprints, SSH keys, Git credentials and exfiltrate them through a multi‑stage C2 chain.
Overview
On 2026‑03‑25 a supply‑chain attack targeted the Apifox desktop client. The official CDN script
https://cdn.apifox.com/www/assets/js/apifox-app-event-tracking.min.js(≈34 KB) was replaced with a malicious version (≈77 KB) that loads additional code from apifox.it.com.
Stage 1 – Reconstructing the Injected Payload
The live CDN now serves a clean file, so the poisoned version was retrieved from the Internet Archive snapshot taken at UTC 2026‑03‑05 05:14:18:
https://web.archive.org/web/20260305051418/https://cdn.apifox.com/www/assets/js/apifox-app-event-tracking.min.js. The file consists of the legitimate tracking code followed by an extra ~40 KB block generated by a function named _0x10e4(). The added block uses string‑array shuffling, RC4‑style decryption, proxy functions, hex constants and anti‑debug tricks. Manual de‑obfuscation was costly; Claude Code was used to extract the C2 URL, polling interval, and the relationship with require('crypto') and require('os'). A readable version of the de‑obfuscated code is available at
https://gist.github.com/phith0n/7020c55bf241b2f3ccf5254192bd48a5.
The restored Stage 1 logic can be summarised as “collect identity → send custom‑header request to C2 → decrypt and eval ”. It runs only in the Electron desktop environment because it relies on Node‑style modules ( require('crypto'), require('os')) that are unavailable in regular browsers.
Fingerprint collection steps:
Read MAC address (excluding loopback and zero values), CPU model, hostname, OS username and OS type via require('os') and require('crypto').
Concatenate these values and compute a SHA‑256 hash; store the result in localStorage._rl_mc and send it as the af_uuid request header.
Send OS version in clear as af_os.
Encrypt username and hostname with an embedded RSA‑2048 private key using privateEncrypt (PKCS#1 v1.5), Base64‑encode the ciphertext, and send as af_user and af_name.
Cache the generated fields in _rl_headers to avoid repeated collection.
If localStorage.common.accessToken (the Apifox access token) exists, the script calls https://api.apifox.com/api/v1/user to obtain the account’s email and display name, encrypts them with the same RSA key, and adds them to the custom headers.
The embedded RSA private key is also used to process C2 responses: the response body is split into 256‑byte blocks, each decrypted with privateDecrypt (OAEP, SHA‑256) to obtain plaintext JavaScript, which is then executed via loadAndExecute. The script fetches https://apifox.it.com/public/apifox-event.js, attaches the custom headers, and evaluates the decrypted payload. Errors inside a try/catch/finally block are silently ignored. The finally clause always calls scheduleNext(), which schedules the next poll after a random interval of 30 minutes to 3 hours, ensuring persistence even if a request fails.
Stage 2 – C2 Identification via Certificate Information
The Stage 1 payload downloads https://apifox.it.com/public/apifox-event.js. The domain apifox.it.com is a typo‑squatted variant of the official domain and no longer resolves publicly. Using a certificate search ( cert:"apifox.it.com") in Quake, the server was located at IP 13.192.121.27 (Tokyo AWS EC2) running nginx 1.28.2 with an Express backend. Direct IP access returns HTTP 404, indicating host‑header filtering.
The server only returns a useful payload when the client supplies a set of custom headers, e.g. af_uuid, af_os, af_user, af_name, etc. Missing or mismatched headers cause an empty response, which explains why a plain curl request fails.
When the required headers are present, the response body is a 344‑byte RSA ciphertext. Using the extracted private key, the ciphertext is decrypted with rsaDecrypt (OAEP, SHA‑256) to obtain an IIFE that inserts <script src="https://apifox.it.com/02ab429d.js"> into document.head. After the script loads, its onload handler removes the script node, reducing the chance of leaving a visible tag in the DOM.
Stage 3 – Plain‑text Data‑Exfiltration Payload
The third‑stage script 02ab429d.js (≈3.6 KB, unobfuscated) performs the following actions:
On macOS/Linux, recursively read ~/.ssh/, read .zsh_history, .bash_history, and .git-credentials, then execute ps aux.
On Windows, read %USERPROFILE%\.ssh\ and execute tasklist.
The collected data is JSON‑stringified, gzip‑compressed, and encrypted with AES‑256‑GCM using a key derived via scrypt("apifox", "foxapi", 32). The ciphertext is posted to https://apifox.it.com/event/0/log with Content‑Type: text/plain to blend with normal traffic.
Detection Indicators
Use of Apifox desktop client before the official fix on 2026‑03‑04 with a version lower than 2.8.19.
Outbound connections to apifox.it.com or related paths ( /public/apifox-event.js, /02ab429d.js, /event/0/log) observed in firewall/DNS logs.
Presence of keys _rl_mc or _rl_headers in Electron DevTools → Application → Local Storage, containing fields such as af_uuid, af_user, etc.
A file named “Network Persistent State” (no extension) under the Apifox user‑data directory containing the string apifox.it.com, or matching entries in the LevelDB storage ( rl_mc, rl_headers).
Remediation Steps (Prioritized)
Upgrade to Apifox 2.8.19 or later, which embeds the tracking script in the installer and stops CDN‑based injection.
Regenerate SSH keys after backing up, remove old public keys from servers, and audit auth.log for anomalous logins.
If ~/.git-credentials exists, delete it, revoke and recreate personal access tokens on GitHub/GitLab, change passwords, and switch to system credential helpers (e.g., osxkeychain, manager).
Rotate any secrets found in .zsh_history, .bash_history, or other shell histories (database passwords, API keys, cloud provider access keys).
Change the Apifox account password and re‑login to refresh the access token.
Block known malicious domains ( apifox.it.com, cdn.openroute.dev, upgrade.feishu.it.com) at the firewall/DNS level, then uninstall Apifox and reinstall the latest official version.
Conclusion
The attack demonstrates a classic supply‑chain compromise: a legitimate CDN‑delivered script was hijacked, extended with obfuscated JavaScript, and used to launch a multi‑stage credential‑stealing chain that leverages Electron’s Node APIs. Detection relies on archive retrieval, certificate‑based C2 discovery, and inspection of custom local‑storage keys. Mitigation focuses on upgrading the client, blocking the malicious domains, and rotating any potentially leaked secrets.
Black & White Path
We are the beacon of the cyber world, a stepping stone on the road to security.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
