Skip to content

Instantly share code, notes, and snippets.

@StoneyEagle
Last active March 31, 2026 05:34
Show Gist options
  • Select an option

  • Save StoneyEagle/f2ee5fd81d94c3fcd1993422462b6916 to your computer and use it in GitHub Desktop.

Select an option

Save StoneyEagle/f2ee5fd81d94c3fcd1993422462b6916 to your computer and use it in GitHub Desktop.
Scan for axios RAT (system.bat) — registry, startup, scheduled tasks

Axios NPM Supply Chain Attack Scanner

The Attack

On March 31, 2026, one of the most operationally sophisticated supply chain attacks ever documented hit axios — an npm package with 83 million weekly downloads and adoption across virtually every JavaScript ecosystem.

What happened

An attacker compromised the npm credentials of jasonsaayman, the lead maintainer of axios. The attacker:

  1. Obtained a long-lived npm access token (not the ephemeral OIDC tokens used by the CI/CD pipeline)
  2. Changed the account's registered email to ifstap@proton.me (an anonymous ProtonMail address)
  3. Published two poisoned versions directly via the npm CLI, bypassing GitHub Actions entirely
  4. Targeted both release branches within 39 minutes to maximize blast radius

Timeline (UTC)

Time Event
Mar 30, 05:57 plain-crypto-js@4.2.0 published — clean decoy to establish the package
Mar 30, 23:59 plain-crypto-js@4.2.1 published — malicious postinstall payload added
Mar 31, 00:05 Socket automated detection flags plain-crypto-js as anomalous
Mar 31, 00:21 axios@1.14.1 published via compromised account — targets 1.x users
Mar 31, 01:00 axios@0.30.4 published — targets legacy 0.x users (39 min later)
Mar 31, 01:30 StepSecurity Harden-Runner captures live C2 callbacks during npm install
Mar 31, ~03:00 GitHub issue filed, community mobilizes
Mar 31, 03:06 Co-maintainer @DigitalBrainJS confirms jasonsaayman's credentials are compromised but cannot revoke access (collaborator, not admin)
Mar 31, 03:20 @DigitalBrainJS contacts npm administration
Mar 31, 03:29 plain-crypto-js removed from npm
Mar 31, 03:40 npm administration revokes all compromised tokens and removes poisoned versions

The attacker timed the release for UTC midnight to maximize the window before humans could respond. The compromised versions were live for approximately 3 hours and 40 minutes.

During incident response, @DigitalBrainJS confirmed the account takeover by pinning a GitHub issue and watching it get unpinned by the attacker using jasonsaayman's stolen credentials. The original issue (#10590) was deleted by the attacker. From the follow-up thread:

"Since access to git and the npm repository is compromised, and his git permissions are higher than mine. I'm a collaborator, not an admin. I can't revoke his access. Whatever I fix, he [can undo]."


The Payload

Injection method

Both axios@1.14.1 and axios@0.30.4 add a single dependency: plain-crypto-js@4.2.1. This package is never imported by axios — its only purpose is to run a postinstall script (setup.js) that deploys a cross-platform RAT.

Obfuscation

setup.js uses two-layer string obfuscation:

  • _trans_1(x, r): XOR cipher with key derived from "OrDeR_7077" (only digits survive: [0,0,0,0,0,0,7,0,7,7])
  • _trans_2(x, r): Reverses encoded strings, replaces underscores with equals signs, base64-decodes, then applies _trans_1

All sensitive data (module names, URLs, shell commands) are stored in an obfuscated array stq[].

Platform-specific attack chains

macOS (darwin):

osascript → AppleScript writes to temp
  → curl downloads RAT to /Library/Caches/com.apple.act.mond
  → /bin/zsh launches with nohup in background

Windows (win32):

where powershell → locates PowerShell binary
  → copies to %PROGRAMDATA%\wt.exe (disguised as Windows Terminal)
  → writes VBScript to %TEMP%\6202033.vbs
  → cscript executes VBScript
  → hidden cmd.exe runs curl to fetch PowerShell RAT
  → wt.exe (PowerShell) executes with -ExecutionPolicy Bypass -WindowStyle Hidden

Linux:

curl → downloads Python script to /tmp/ld.py
  → nohup python3 launches in background
  → detaches from process tree (ppid: 1)

Self-cleanup

After payload delivery, setup.js:

  1. Deletes itself via fs.unlink(__filename)
  2. Deletes the incriminating package.json (which contains the postinstall hook)
  3. Renames a pre-staged clean package.md to package.json (shows version 4.2.0, no postinstall)

Result: Inspecting node_modules/plain-crypto-js/ after infection shows no evidence of compromise.

C2 Communication

Server: sfrclak.com:8000 (142.11.206.73)

Beacon URL: http://sfrclak.com:8000/6202033

POST body identifiers (designed to look like npm registry traffic in network logs):

  • macOS: packages.npm.org/product0
  • Windows: packages.npm.org/product1
  • Linux: packages.npm.org/product2

Fake User-Agent: mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) — IE8 on Windows XP, an obvious tell but effective at bypassing naive user-agent filters.

RAT behavior

On first contact, the RAT sends a FirstInfo beacon containing:

  • Unique victim ID
  • OS and architecture
  • Current user and home directory
  • Full directory enumeration targeting credentials:
    • .ssh/ (SSH keys)
    • .aws/, .s3cfg (AWS credentials)
    • .bashrc, .profile (shell config with potential secrets)
    • .env files

The C2 responds with second-stage payloads for persistent access.


Indicators of Compromise

Compromised packages

Package Version SHA-1
axios 1.14.1 2553649f2322049666871cea80a5d0d6adc700ca
axios 0.30.4 d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71
plain-crypto-js 4.2.1 07d889e2dadce6f3910dcbc253317d28ca61c766

Payload hashes (SHA-256)

Hash Platform
f7d335205b8d7b20208fb3ef93ee6dc817905dc3ae0c10a0b164f4e7d07121cd Windows Stage 1 (PowerShell stager)
617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 Windows Stage 2 (PowerShell RAT)
92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a macOS (Mach-O universal binary)
fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf Linux (Python script)

Network indicators

Type Value
C2 Domain sfrclak.com
C2 IP 142.11.206.73
C2 Port 8000
C2 URL http://sfrclak.com:8000/6202033
Attacker email ifstap@proton.me

File system artifacts

Platform Path Description
Windows %PROGRAMDATA%\wt.exe PowerShell binary disguised as Windows Terminal
Windows %TEMP%\6202033.vbs VBScript dropper (self-deletes)
Windows %TEMP%\6202033.ps1 PowerShell RAT script (self-deletes)
macOS /Library/Caches/com.apple.act.mond RAT binary disguised as Apple system cache
Linux /tmp/ld.py Python RAT payload
All node_modules/plain-crypto-js/ Presence indicates dropper executed

Detection patterns

Pattern Context
FirstInfo / FirstReqPath in JSON RAT beacon payload
packages.npm.org/product in POST body C2 beacon disguised as npm traffic
msie 8.0; windows nt 5.1; trident/4.0 Fake IE8 User-Agent used by RAT
Process: nohup python3 /tmp/ld.py Linux RAT execution
Process tree: node setup.js -> sh -> curl Postinstall dropper chain

Safe versions

Branch Safe version
1.x axios@1.14.0 (shasum: 7c29f4cf2ea91ef05018d5aa5399bf23ed3120eb)
0.x axios@0.30.3 (shasum: ab1be887a2d37dd9ebc219657704180faf2c4920)

Pin exact versions — remove ^ and ~ prefixes from package.json.


Scanners

This gist includes two scanners:

scan-axios-rat.bat (Windows)

12-point check covering registry persistence, RAT file artifacts, C2 connections, DNS resolution, beacon patterns in logs, SHA256 hash matching, lockfile scanning, and compromised axios version detection. Returns exit code 1 if infected.

scan-axios-rat-linux.sh (Linux/macOS)

By @silascutler (original gist). Scans package manifests, lockfiles, node_modules metadata, JavaScript/TypeScript source files, and performs SHA256 hash matching against all known payloads. Generates a detailed report file.


If you are infected

  1. Disconnect from network immediately
  2. Do NOT attempt to clean in place — rebuild from a known-good state
  3. Rotate ALL credentials accessible from the compromised machine:
    • npm tokens
    • AWS/GCP/Azure keys (the RAT specifically targets .s3cfg and .aws/)
    • SSH keys (the RAT enumerates .ssh/)
    • CI/CD secrets
    • .env values
    • Database passwords
  4. Block C2 at firewall: sfrclak.com / 142.11.206.73
  5. Audit network logs for POST requests to port 8000 with the fake IE8 User-Agent
  6. Check for data exfiltration — the RAT sends full directory listings on first contact

Lessons

  • Pin exact dependency versions"axios": "1.14.0" not "^1.13.5"
  • Use lockfiles and commit them
  • npm long-lived tokens are dangerous — use OIDC Trusted Publishing where possible
  • Postinstall scripts are an attack vector — consider --ignore-scripts and explicitly allow-listing
  • The self-cleanup is realnode_modules inspection after the fact shows nothing. You need to catch it at install time or scan for the dropped artifacts
  • Late-night timing is intentional — attackers choose moments when human response is slowest

Sources

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
REPORT_PATH="${PWD}/axios-compromise-scan-${TIMESTAMP}.log"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
declare -a SYSTEM_TARGETS=(
/home
/root
/etc
/opt
/srv
/usr/local
/var/www
)
declare -a EXTRA_TARGETS=()
declare -a TARGETS=()
SCAN_SYSTEM=0
SCAN_ALL_MOUNTS=0
VERBOSE=0
MAX_HASH_MB=50
declare -A HASH_LABELS=(
[f7d335205b8d7b20208fb3ef93ee6dc817905dc3ae0c10a0b164f4e7d07121cd]="Windows Stage 1 PowerShell stager"
[617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101]="Windows Stage 2 PowerShell RAT"
[92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a]="macOS Mach-O universal binary"
[fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf]="Linux Python script"
)
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [options] [path ...]
Scan for indicators tied to the March 31, 2026 Axios npm compromise:
- axios@1.14.1
- axios@0.30.4
- plain-crypto-js@4.2.1
- exact SHA-256 matches for known follow-on payloads
If no paths are provided, the script scans the current user's home directory.
Options:
--system Also scan common system locations (${SYSTEM_TARGETS[*]})
--all-mounts Allow crossing filesystem boundaries
--max-hash-size-mb Only hash files up to this size in MB (default: $MAX_HASH_MB)
--report PATH Write the report to PATH
-v, --verbose Print progress to stderr
-h, --help Show this help text
Examples:
$SCRIPT_NAME
$SCRIPT_NAME --system
$SCRIPT_NAME /home /srv/app
EOF
}
log() {
if [[ "$VERBOSE" -eq 1 ]]; then
printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" >&2
fi
}
note() {
printf '%s\n' "$*" | tee -a "$REPORT_PATH"
}
stderr_file() {
printf '%s\n' "$TMP_DIR/find.stderr"
}
record_permission_denials() {
local denied_root_file="$TMP_DIR/denied_roots.txt"
: > "$denied_root_file"
if [[ -s "$(stderr_file)" ]]; then
while IFS= read -r line; do
[[ "$line" == *"Permission denied"* ]] || continue
for target in "${TARGETS[@]}"; do
if [[ "$line" == *"$target"* ]]; then
printf '%s\n' "$target" >> "$denied_root_file"
break
fi
done
done < "$(stderr_file)"
fi
if [[ -s "$denied_root_file" ]]; then
sort -u "$denied_root_file" -o "$denied_root_file"
fi
}
append_matches() {
local title="$1"
local file="$2"
if [[ -s "$file" ]]; then
note ""
note "## $title"
tee -a "$REPORT_PATH" < "$file"
fi
}
is_excluded_dir() {
case "$1" in
/proc|/proc/*|/sys|/sys/*|/dev|/dev/*|/run|/run/*|/tmp|/tmp/*|/var/tmp|/var/tmp/*|/snap|/snap/*)
return 0
;;
*)
return 1
;;
esac
}
collect_targets() {
local candidate
if [[ "${#EXTRA_TARGETS[@]}" -gt 0 ]]; then
TARGETS=("${EXTRA_TARGETS[@]}")
else
TARGETS=("${HOME}")
fi
if [[ "$SCAN_SYSTEM" -eq 1 ]]; then
for candidate in "${SYSTEM_TARGETS[@]}"; do
TARGETS+=("$candidate")
done
fi
local deduped=()
declare -A seen=()
for candidate in "${TARGETS[@]}"; do
[[ -e "$candidate" ]] || continue
is_excluded_dir "$candidate" && continue
if [[ -z "${seen["$candidate"]+x}" ]]; then
seen["$candidate"]=1
deduped+=("$candidate")
fi
done
TARGETS=("${deduped[@]}")
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--system)
SCAN_SYSTEM=1
shift
;;
--all-mounts)
SCAN_ALL_MOUNTS=1
shift
;;
--report)
REPORT_PATH="$2"
shift 2
;;
--max-hash-size-mb)
MAX_HASH_MB="$2"
shift 2
;;
-v|--verbose)
VERBOSE=1
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
EXTRA_TARGETS+=("$1")
shift
done
;;
-*)
printf 'Unknown option: %s\n\n' "$1" >&2
usage >&2
exit 1
;;
*)
EXTRA_TARGETS+=("$1")
shift
;;
esac
done
}
find_args() {
if [[ "$SCAN_ALL_MOUNTS" -eq 1 ]]; then
printf '%s\n' ""
else
printf '%s\n' "-xdev"
fi
}
main() {
parse_args "$@"
collect_targets
if [[ "${#TARGETS[@]}" -eq 0 ]]; then
printf 'No valid scan targets were found.\n' >&2
exit 1
fi
: > "$REPORT_PATH"
note "# Axios compromise scan report"
note "Date: $(date -Is)"
note "Host: $(hostname)"
note "Targets: ${TARGETS[*]}"
note "Cross filesystems: $([[ "$SCAN_ALL_MOUNTS" -eq 1 ]] && printf yes || printf no)"
note "Indicators: axios@1.14.1, axios@0.30.4, plain-crypto-js@4.2.1"
note "Hash IOCs: ${!HASH_LABELS[*]}"
note "Max hashed file size: ${MAX_HASH_MB} MB"
local lock_hits="$TMP_DIR/lock_hits.txt"
local pkg_hits="$TMP_DIR/pkg_hits.txt"
local nm_hits="$TMP_DIR/node_module_hits.txt"
local generic_hits="$TMP_DIR/generic_hits.txt"
local hash_hits="$TMP_DIR/hash_hits.txt"
local find_xdev
local abs_script
local find_stderr
local max_hash_kib
abs_script="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
find_stderr="$(stderr_file)"
: > "$find_stderr"
max_hash_kib=$((MAX_HASH_MB * 1024 + 1))
find_xdev="$(find_args)"
log "Scanning package manifests and lockfiles"
if [[ -n "$find_xdev" ]]; then
find "${TARGETS[@]}" "$find_xdev" \
\( -path '*/.git' -o -path '*/node_modules/.cache' -o -path '*/.cache' \) -prune -o \
-type f \
\( -name 'package.json' -o -name 'package-lock.json' -o -name 'npm-shrinkwrap.json' -o -name 'yarn.lock' -o -name 'pnpm-lock.yaml' \) \
-readable ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | xargs -0r grep -nHIE '"axios"[[:space:]]*:[[:space:]]*"?(\^|~)?(1\.14\.1|0\.30\.4)"?|"plain-crypto-js"[[:space:]]*:[[:space:]]*"?(\^|~)?4\.2\.1"?|axios@1\.14\.1|axios@0\.30\.4|plain-crypto-js@4\.2\.1' > "$lock_hits" || true
else
find "${TARGETS[@]}" \
\( -path '*/.git' -o -path '*/node_modules/.cache' -o -path '*/.cache' \) -prune -o \
-type f \
\( -name 'package.json' -o -name 'package-lock.json' -o -name 'npm-shrinkwrap.json' -o -name 'yarn.lock' -o -name 'pnpm-lock.yaml' \) \
-readable ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | xargs -0r grep -nHIE '"axios"[[:space:]]*:[[:space:]]*"?(\^|~)?(1\.14\.1|0\.30\.4)"?|"plain-crypto-js"[[:space:]]*:[[:space:]]*"?(\^|~)?4\.2\.1"?|axios@1\.14\.1|axios@0\.30\.4|plain-crypto-js@4\.2\.1' > "$lock_hits" || true
fi
log "Checking installed package metadata inside node_modules"
if [[ -n "$find_xdev" ]]; then
find "${TARGETS[@]}" "$find_xdev" \
\( -path '*/node_modules/axios/package.json' -o -path '*/node_modules/plain-crypto-js/package.json' \) \
-type f -readable ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | while IFS= read -r -d '' file; do
grep -nHIE '"name"[[:space:]]*:[[:space:]]*"(axios|plain-crypto-js)"|"version"[[:space:]]*:[[:space:]]*"(1\.14\.1|0\.30\.4|4\.2\.1)"' "$file"
done > "$nm_hits" || true
else
find "${TARGETS[@]}" \
\( -path '*/node_modules/axios/package.json' -o -path '*/node_modules/plain-crypto-js/package.json' \) \
-type f -readable ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | while IFS= read -r -d '' file; do
grep -nHIE '"name"[[:space:]]*:[[:space:]]*"(axios|plain-crypto-js)"|"version"[[:space:]]*:[[:space:]]*"(1\.14\.1|0\.30\.4|4\.2\.1)"' "$file"
done > "$nm_hits" || true
fi
log "Searching JavaScript/TypeScript source and shell snippets for suspicious references"
if [[ -n "$find_xdev" ]]; then
find "${TARGETS[@]}" "$find_xdev" \
\( -path '*/.git' -o -path '*/dist' -o -path '*/build' -o -path '*/coverage' \) -prune -o \
-type f \
\( -name '*.js' -o -name '*.cjs' -o -name '*.mjs' -o -name '*.ts' -o -name '*.sh' -o -name '*.env' \) \
-readable ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | xargs -0r grep -nHIE 'plain-crypto-js|axios@1\.14\.1|axios@0\.30\.4' > "$generic_hits" || true
else
find "${TARGETS[@]}" \
\( -path '*/.git' -o -path '*/dist' -o -path '*/build' -o -path '*/coverage' \) -prune -o \
-type f \
\( -name '*.js' -o -name '*.cjs' -o -name '*.mjs' -o -name '*.ts' -o -name '*.sh' -o -name '*.env' \) \
-readable ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | xargs -0r grep -nHIE 'plain-crypto-js|axios@1\.14\.1|axios@0\.30\.4' > "$generic_hits" || true
fi
log "Hashing small readable files for exact IOC matches"
: > "$hash_hits"
if command -v sha256sum >/dev/null 2>&1; then
if [[ -n "$find_xdev" ]]; then
find "${TARGETS[@]}" "$find_xdev" \
\( -path '*/.git' -o -path '*/node_modules/.cache' -o -path '*/.cache' -o -path '*/dist' -o -path '*/build' -o -path '*/coverage' \) -prune -o \
-type f -readable -size -"${max_hash_kib}"k ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | while IFS= read -r -d '' file; do
hash_value="$(sha256sum "$file" | awk '{print $1}')"
if [[ -n "${HASH_LABELS[$hash_value]+x}" ]]; then
printf '%s: sha256=%s (%s)\n' "$file" "$hash_value" "${HASH_LABELS[$hash_value]}"
fi
done > "$hash_hits" || true
else
find "${TARGETS[@]}" \
\( -path '*/.git' -o -path '*/node_modules/.cache' -o -path '*/.cache' -o -path '*/dist' -o -path '*/build' -o -path '*/coverage' \) -prune -o \
-type f -readable -size -"${max_hash_kib}"k ! -samefile "$abs_script" ! -samefile "$REPORT_PATH" -print0 2>>"$find_stderr" | while IFS= read -r -d '' file; do
hash_value="$(sha256sum "$file" | awk '{print $1}')"
if [[ -n "${HASH_LABELS[$hash_value]+x}" ]]; then
printf '%s: sha256=%s (%s)\n' "$file" "$hash_value" "${HASH_LABELS[$hash_value]}"
fi
done > "$hash_hits" || true
fi
else
printf 'sha256sum is not available on this system.\n' > "$hash_hits"
fi
log "Classifying high-confidence package.json hits"
if [[ -s "$nm_hits" ]]; then
cut -d: -f1 "$nm_hits" | sort -u | while IFS= read -r pkg_file; do
[[ -n "$pkg_file" ]] || continue
pkg_name="$(
sed -nE 's/^[[:space:]]*"name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$pkg_file" | head -n1
)"
pkg_version="$(
sed -nE 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$pkg_file" | head -n1
)"
if [[ "$pkg_name" =~ ^(axios|plain-crypto-js)$ ]] && [[ "$pkg_version" =~ ^(1\.14\.1|0\.30\.4|4\.2\.1)$ ]]; then
printf '%s: package=%s version=%s\n' "$pkg_file" "$pkg_name" "$pkg_version"
fi
done > "$pkg_hits" || true
fi
append_matches "High-confidence installed package hits" "$pkg_hits"
append_matches "Exact file hash matches" "$hash_hits"
append_matches "Manifest and lockfile references" "$lock_hits"
append_matches "node_modules package.json evidence" "$nm_hits"
append_matches "Source/script references" "$generic_hits"
record_permission_denials
local total_hits=0
for file in "$pkg_hits" "$hash_hits" "$lock_hits" "$nm_hits" "$generic_hits"; do
if [[ -s "$file" ]]; then
total_hits=$((total_hits + $(wc -l < "$file")))
fi
done
note ""
if [[ "$total_hits" -eq 0 ]]; then
note "Result: no indicators found in readable files under the selected targets."
else
note "Result: $total_hits matching lines found. Review the sections above for paths and versions."
fi
if [[ -s "$TMP_DIR/denied_roots.txt" ]]; then
note ""
note "Inaccessible root targets:"
tee -a "$REPORT_PATH" < "$TMP_DIR/denied_roots.txt"
fi
note "Report saved to: $REPORT_PATH"
}
main "$@"
@echo off
setlocal enabledelayedexpansion
set "FOUND=0"
echo ============================================
echo Axios RAT Scanner - Supply Chain Attack
echo Compromised: axios@1.14.1, axios@0.30.4
echo Dependency: plain-crypto-js@4.2.1
echo C2: sfrclak.com / 142.11.206.73:8000
echo ============================================
echo.
echo Payload SHA256 Hashes:
echo Win Stage 1: f7d335205b8d7b20208fb3ef93ee6dc817905dc3ae0c10a0b164f4e7d07121cd
echo Win Stage 2: 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101
echo macOS: 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a
echo Linux: fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf
echo.
echo Safe versions: axios@1.14.0 / axios@0.30.3
echo ============================================
echo.
echo [1/12] Checking for poisoned npm package...
set "CHECK_HIT="
for /f "delims=" %%d in ('dir /s /b /ad "node_modules\plain-crypto-js" 2^>nul') do (
echo [!!] FOUND plain-crypto-js at: %%d
set "FOUND=1"
set "CHECK_HIT=1"
)
if not defined CHECK_HIT echo [OK] plain-crypto-js not found
echo [2/12] Checking for RAT artifacts (Windows)...
set "CHECK_HIT="
if exist "%PROGRAMDATA%\wt.exe" (
echo [!!] FOUND RAT: %PROGRAMDATA%\wt.exe
echo PowerShell copy disguised as Windows Terminal
set "FOUND=1"
set "CHECK_HIT=1"
)
if exist "%TEMP%\6202033.vbs" (
echo [!!] FOUND dropper: %TEMP%\6202033.vbs
set "FOUND=1"
set "CHECK_HIT=1"
)
if exist "%TEMP%\6202033.ps1" (
echo [!!] FOUND RAT script: %TEMP%\6202033.ps1
set "FOUND=1"
set "CHECK_HIT=1"
)
if not defined CHECK_HIT echo [OK] No RAT files found
echo [3/12] Checking for RAT artifacts (cross-platform)...
set "CHECK_HIT="
for %%F in (
"C:\tmp\ld.py"
"%TEMP%\ld.py"
"%TEMP%\mal.py"
) do (
if exist %%F (
echo [!!] FOUND RAT payload: %%F
set "FOUND=1"
set "CHECK_HIT=1"
)
)
if not defined CHECK_HIT echo [OK] No cross-platform RAT files found
echo [4/12] Checking Run keys for persistence...
set "CHECK_HIT="
for %%K in (
"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
) do (
reg query %%K /s 2>nul | findstr /i "wt.exe system.bat 6202033 sfrclak plain-crypto" >nul 2>nul && (
echo [!!] FOUND suspicious entry in %%K:
reg query %%K /s 2>nul | findstr /i "wt.exe system.bat 6202033 sfrclak plain-crypto"
set "FOUND=1"
set "CHECK_HIT=1"
)
)
if not defined CHECK_HIT echo [OK] No suspicious Run entries
echo [5/12] Checking WindowsUpdate registry keys...
set "CHECK_HIT="
for %%K in (
"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate"
"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate"
"HKLM\SOFTWARE\Microsoft\WindowsUpdate"
) do (
reg query %%K /s 2>nul | findstr /i "system.bat wt.exe sfrclak 6202033" >nul 2>nul && (
echo [!!] FOUND in %%K
reg query %%K /s 2>nul | findstr /i "system.bat wt.exe sfrclak 6202033"
set "FOUND=1"
set "CHECK_HIT=1"
)
)
if not defined CHECK_HIT echo [OK] No WindowsUpdate tampering
echo [6/12] Full registry search...
set "CHECK_HIT="
for %%H in (HKLM HKCU) do (
for %%T in (sfrclak system.bat plain-crypto 6202033) do (
reg query "%%H\SOFTWARE" /s /f "%%T" 2>nul | findstr /i "%%T" >nul 2>nul && (
echo [!!] FOUND "%%T" in %%H\SOFTWARE:
reg query "%%H\SOFTWARE" /s /f "%%T" 2>nul | findstr /i "%%T"
set "FOUND=1"
set "CHECK_HIT=1"
)
)
)
if not defined CHECK_HIT echo [OK] No registry IOCs
echo [7/12] Checking scheduled tasks...
set "CHECK_HIT="
schtasks /query /fo LIST /v 2>nul | findstr /i "system.bat wt.exe sfrclak 6202033 plain-crypto ld.py mal.py" >nul 2>nul && (
echo [!!] FOUND suspicious scheduled task:
schtasks /query /fo LIST /v 2>nul | findstr /i "system.bat wt.exe sfrclak 6202033 plain-crypto ld.py mal.py"
set "FOUND=1"
set "CHECK_HIT=1"
)
if not defined CHECK_HIT echo [OK] No suspicious tasks
echo [8/12] Checking for C2 connections and DNS...
set "CHECK_HIT="
netstat -an 2>nul | findstr "142.11.206.73" >nul 2>nul && (
echo [!!] ACTIVE CONNECTION to C2 server 142.11.206.73!
netstat -an 2>nul | findstr "142.11.206.73"
set "FOUND=1"
set "CHECK_HIT=1"
)
netstat -an 2>nul | findstr "127.0.0.1:8000" >nul 2>nul && (
echo [??] localhost:8000 is active - verify this is not the RAT local proxy
echo RAT uses POST to 127.0.0.1:8000 with fake IE8 User-Agent
)
nslookup sfrclak.com 2>nul | findstr "142.11.206.73" >nul 2>nul && (
echo [!!] C2 domain sfrclak.com resolves to 142.11.206.73
echo Block at DNS/firewall immediately
set "CHECK_HIT=1"
)
if not defined CHECK_HIT echo [OK] No C2 connections
echo [9/12] Checking for RAT beacon patterns in logs...
set "CHECK_HIT="
for /f "delims=" %%f in ('dir /s /b "%TEMP%\*.log" "%TEMP%\*.txt" "%APPDATA%\*.log" 2^>nul') do (
findstr /i "FirstInfo FirstReqPath sfrclak plain-crypto-js packages.npm.org/product" "%%f" >nul 2>nul && (
echo [!!] FOUND RAT beacon data in: %%f
set "FOUND=1"
set "CHECK_HIT=1"
)
)
for /f "delims=" %%f in ('dir /s /b "%PROGRAMDATA%\*.log" 2^>nul') do (
findstr /c:"msie 8.0" "%%f" >nul 2>nul && (
echo [!!] FOUND fake IE8 User-Agent in: %%f
echo RAT beacon: mozilla/4.0 compatible msie 8.0 windows nt 5.1 trident/4.0
set "FOUND=1"
set "CHECK_HIT=1"
)
)
if not defined CHECK_HIT echo [OK] No beacon patterns found
echo [10/12] Checking installed axios versions...
set "CHECK_HIT="
for /f "delims=" %%d in ('dir /s /b /ad "node_modules\axios" 2^>nul') do (
for /f "tokens=2 delims=:" %%v in ('findstr /c:"\"version\"" "%%d\package.json" 2^>nul') do (
set "VER=%%v"
set "VER=!VER: =!"
set "VER=!VER:"=!"
set "VER=!VER:,=!"
if "!VER!"=="1.14.1" (
echo [!!] COMPROMISED axios@1.14.1 in %%d
set "FOUND=1"
set "CHECK_HIT=1"
) else if "!VER!"=="0.30.4" (
echo [!!] COMPROMISED axios@0.30.4 in %%d
set "FOUND=1"
set "CHECK_HIT=1"
) else (
echo [--] axios@!VER! in %%d
)
)
)
if not defined CHECK_HIT if defined VER echo [OK] No compromised axios versions
echo [11/12] SHA256 hash scan for known payloads...
set "CHECK_HIT="
where certutil >nul 2>nul && (
for /f "delims=" %%f in ('dir /s /b "%PROGRAMDATA%\wt.exe" "%TEMP%\6202033.ps1" "%TEMP%\6202033.vbs" "%TEMP%\ld.py" "%TEMP%\mal.py" 2^>nul') do (
for /f "skip=1 tokens=*" %%h in ('certutil -hashfile "%%f" SHA256 2^>nul ^| findstr /r "^[0-9a-f]"') do (
set "HASH=%%h"
set "HASH=!HASH: =!"
if "!HASH!"=="f7d335205b8d7b20208fb3ef93ee6dc817905dc3ae0c10a0b164f4e7d07121cd" (
echo [!!] HASH MATCH: Win Stage 1 stager - %%f
set "FOUND=1"
set "CHECK_HIT=1"
)
if "!HASH!"=="617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101" (
echo [!!] HASH MATCH: Win Stage 2 RAT - %%f
set "FOUND=1"
set "CHECK_HIT=1"
)
if "!HASH!"=="92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a" (
echo [!!] HASH MATCH: macOS Mach-O RAT - %%f
set "FOUND=1"
set "CHECK_HIT=1"
)
if "!HASH!"=="fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf" (
echo [!!] HASH MATCH: Linux Python RAT - %%f
set "FOUND=1"
set "CHECK_HIT=1"
)
)
)
)
if not defined CHECK_HIT echo [OK] No payload hash matches
echo [12/12] Checking lockfiles for compromised references...
set "CHECK_HIT="
for /f "delims=" %%f in ('dir /s /b "package-lock.json" "yarn.lock" "pnpm-lock.yaml" "npm-shrinkwrap.json" 2^>nul ^| findstr /v "node_modules"') do (
findstr /i "plain-crypto-js axios@1.14.1 axios@0.30.4" "%%f" >nul 2>nul && (
echo [!!] FOUND compromised reference in: %%f
findstr /i "plain-crypto-js axios@1.14.1 axios@0.30.4" "%%f"
set "FOUND=1"
set "CHECK_HIT=1"
)
)
if not defined CHECK_HIT echo [OK] No compromised lockfile references
echo.
echo ============================================
if "%FOUND%"=="1" (
echo [!!] INFECTED - IOCs detected!
echo.
echo Immediate actions:
echo 1. Disconnect from network
echo 2. Do NOT clean in place - rebuild from known-good
echo 3. Rotate ALL credentials:
echo - npm tokens
echo - AWS/GCP/Azure keys [check .s3cfg, .aws/]
echo - SSH keys [.ssh/]
echo - CI/CD secrets
echo - .env values
echo - Database passwords
echo 4. Block C2: sfrclak.com / 142.11.206.73
echo 5. Pin axios to 1.14.0 or 0.30.3
echo 6. Check for data exfiltration:
echo - RAT sends FirstInfo beacon with full dir listing
echo - Targets: .ssh/ .aws/ .s3cfg .env .bashrc
echo - POST to C2 with fake IE8 User-Agent
echo ============================================
exit /b 1
) else (
echo [OK] CLEAN - no axios RAT indicators found.
echo.
echo Recommendations:
echo - Pin axios: 1.14.0 [1.x] or 0.30.3 [0.x]
echo - Block C2 at firewall: sfrclak.com / 142.11.206.73
echo - Add plain-crypto-js to npm audit deny list
echo - Monitor for fake IE8 User-Agent in network logs
echo ============================================
exit /b 0
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment