Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- # ============================================================================
- # AXIOS SUPPLY CHAIN COMPROMISE — FLEET IOC HUNT SCRIPT
- # Author: karmine05
- # Date: 2026-03-31
- #
- # Runs via Fleet Scripts on macOS and Linux endpoints.
- # Checks file artifacts, SHA256 hashes, malicious npm packages, running
- # processes, codesign abuse, network connections, DNS, and persistence.
- # ============================================================================
- set -u
- COMPROMISED=0
- FINDINGS=""
- # --- Known SHA256 hashes (Wiz Research IOC appendix) ------------------------
- HASH_MACOS_RAT="92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a"
- HASH_WIN_STAGE2="617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101"
- HASH_LINUX_RAT="fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf"
- HASH_DROPPER="58401c195fe0a6204b42f5f90995ece5fab74ce7c69c67a24c61a057325af668"
- HASH_AXIOS_0304="59336a964f110c25c112bcc5adca7090296b54ab33fa95c0744b94f8a0d80c0f"
- HASH_AXIOS_1141="5bb67e88846096f1f8d42a0f0350c9c46260591567612ff9af46f98d1b7571cd"
- # --- Helpers ----------------------------------------------------------------
- log_finding() {
- local category="$1"
- local detail="$2"
- COMPROMISED=1
- FINDINGS="${FINDINGS}\n[HIT] ${category}: ${detail}"
- printf "[HIT] %s: %s\n" "$category" "$detail"
- }
- log_clean() {
- echo "[OK] $1: No indicators found"
- }
- detect_os() {
- case "$(uname -s)" in
- Darwin*) echo "macos" ;;
- Linux*) echo "linux" ;;
- *) echo "unknown" ;;
- esac
- }
- # Cross-platform SHA256 helper
- sha256_of() {
- local filepath="$1"
- if command -v shasum &>/dev/null; then
- shasum -a 256 "$filepath" 2>/dev/null | awk '{print $1}'
- elif command -v sha256sum &>/dev/null; then
- sha256sum "$filepath" 2>/dev/null | awk '{print $1}'
- else
- echo "NO_SHA256_TOOL"
- fi
- }
- OS=$(detect_os)
- echo "============================================================"
- echo " Axios Supply Chain IOC Hunt (v2 — Wiz Research IOCs)"
- echo " Host: $(hostname)"
- echo " OS: ${OS} ($(uname -s) $(uname -r))"
- echo " Date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
- echo "============================================================"
- echo ""
- # ============================================================================
- # 1. FILE SYSTEM ARTIFACTS
- # ============================================================================
- echo "--- [1/9] File System Artifacts ---"
- FOUND_FILES=0
- # macOS RAT payload
- if [ "$OS" = "macos" ]; then
- if [ -f "/Library/Caches/com.apple.act.mond" ]; then
- log_finding "FILE" "macOS RAT payload: /Library/Caches/com.apple.act.mond ($(ls -la /Library/Caches/com.apple.act.mond 2>/dev/null))"
- FOUND_FILES=1
- fi
- fi
- # Linux RAT payload
- if [ "$OS" = "linux" ]; then
- if [ -f "/tmp/ld.py" ]; then
- log_finding "FILE" "Linux RAT payload: /tmp/ld.py ($(ls -la /tmp/ld.py 2>/dev/null))"
- FOUND_FILES=1
- fi
- fi
- # Cross-platform temp staging file
- for tmp_path in "/tmp/6202033" "/private/tmp/6202033" "${TMPDIR:-/tmp}/6202033"; do
- if [ -f "$tmp_path" ]; then
- log_finding "FILE" "Staging file: ${tmp_path} ($(ls -la "$tmp_path" 2>/dev/null))"
- FOUND_FILES=1
- fi
- done
- [ "$FOUND_FILES" -eq 0 ] && log_clean "File artifacts"
- echo ""
- # ============================================================================
- # 2. SHA256 HASH VALIDATION (Wiz Research)
- # ============================================================================
- echo "--- [2/9] SHA256 Hash Validation ---"
- FOUND_HASH=0
- # Build list of (file_path|expected_hash|description) tuples
- HASH_CHECKS=()
- if [ "$OS" = "macos" ]; then
- HASH_CHECKS=(
- "/Library/Caches/com.apple.act.mond|${HASH_MACOS_RAT}|macOS Stage 2 RAT (Mach-O universal binary)"
- )
- elif [ "$OS" = "linux" ]; then
- HASH_CHECKS=(
- "/tmp/ld.py|${HASH_LINUX_RAT}|Linux Stage 2 RAT (Python script)"
- )
- fi
- for check in "${HASH_CHECKS[@]}"; do
- IFS='|' read -r fpath expected_hash description <<< "$check"
- if [ -f "$fpath" ]; then
- actual_hash=$(sha256_of "$fpath")
- if [ "$actual_hash" = "$expected_hash" ]; then
- log_finding "SHA256" "CONFIRMED MATCH: ${description} — ${fpath} (${actual_hash})"
- FOUND_HASH=1
- else
- echo "[INFO] File exists but hash differs: ${fpath}"
- echo " Expected: ${expected_hash}"
- echo " Actual: ${actual_hash}"
- fi
- fi
- done
- # Check npm cache for compromised package tarballs by hash
- if command -v npm &>/dev/null; then
- NPM_CACHE_DIR=$(npm config get cache 2>/dev/null || echo "")
- if [ -n "$NPM_CACHE_DIR" ] && [ -d "$NPM_CACHE_DIR" ]; then
- echo "[INFO] Scanning npm cache at ${NPM_CACHE_DIR} for compromised tarballs..."
- for tarball_hash in "$HASH_AXIOS_0304" "$HASH_AXIOS_1141" "$HASH_DROPPER"; do
- CACHE_HIT=$(find "${NPM_CACHE_DIR}" -type f \( -name "*.tgz" -o -name "*.tar.gz" \) 2>/dev/null | head -100 | while read -r tgz; do
- h=$(sha256_of "$tgz")
- if [ "$h" = "$tarball_hash" ]; then
- echo "$tgz"
- break
- fi
- done || true)
- if [ -n "$CACHE_HIT" ]; then
- log_finding "SHA256" "Compromised tarball in npm cache: ${CACHE_HIT} (sha256: ${tarball_hash})"
- FOUND_HASH=1
- fi
- done
- fi
- fi
- [ "$FOUND_HASH" -eq 0 ] && log_clean "SHA256 hash validation"
- echo ""
- # ============================================================================
- # 3. MALICIOUS NPM PACKAGES
- # ============================================================================
- echo "--- [3/9] Malicious npm Packages ---"
- FOUND_NPM=0
- # Check global npm for compromised axios versions
- if command -v npm &>/dev/null; then
- GLOBAL_AXIOS=$(npm ls -g axios 2>/dev/null | grep -E "axios@(1\.14\.1|0\.30\.4)" || true)
- if [ -n "$GLOBAL_AXIOS" ]; then
- log_finding "NPM-GLOBAL" "Compromised axios installed globally: ${GLOBAL_AXIOS}"
- FOUND_NPM=1
- fi
- fi
- # Search for the dropper dependency and malicious packages in node_modules
- MALICIOUS_PKGS=(
- "plain-crypto-js"
- "@shadanai/openclaw"
- "@qqbrowser/openclaw-qbot"
- )
- SEARCH_ROOTS=()
- if [ "$OS" = "macos" ]; then
- SEARCH_ROOTS=(/Users/*/node_modules /Users/*/*/node_modules /opt/*/node_modules)
- elif [ "$OS" = "linux" ]; then
- SEARCH_ROOTS=(/home/*/node_modules /home/*/*/node_modules /opt/*/node_modules /var/*/node_modules)
- fi
- for root_glob in "${SEARCH_ROOTS[@]}"; do
- for root_dir in $root_glob; do
- [ -d "$root_dir" ] || continue
- for pkg in "${MALICIOUS_PKGS[@]}"; do
- pkg_json="${root_dir}/${pkg}/package.json"
- if [ -f "$pkg_json" ]; then
- pkg_version=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 || echo "unknown")
- log_finding "NPM-LOCAL" "Malicious package found: ${pkg_json} (${pkg_version})"
- FOUND_NPM=1
- fi
- done
- done
- done
- # Look for setup.js dropper (4,209 bytes) — the stage 1.5 installer
- for root_glob in "${SEARCH_ROOTS[@]}"; do
- for root_dir in $root_glob; do
- [ -d "$root_dir" ] || continue
- setup_js="${root_dir}/plain-crypto-js/setup.js"
- if [ -f "$setup_js" ]; then
- log_finding "NPM-LOCAL" "Dropper script found: ${setup_js} ($(ls -la "$setup_js" 2>/dev/null))"
- FOUND_NPM=1
- fi
- done
- done
- # Search for compromised axios versions in local node_modules
- for root_glob in "${SEARCH_ROOTS[@]}"; do
- for root_dir in $root_glob; do
- [ -d "$root_dir" ] || continue
- axios_json="${root_dir}/axios/package.json"
- if [ -f "$axios_json" ]; then
- axios_ver=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$axios_json" 2>/dev/null | head -1 || echo "")
- if echo "$axios_ver" | grep -qE '"(1\.14\.1|0\.30\.4)"'; then
- log_finding "NPM-LOCAL" "Compromised axios: ${axios_json} (${axios_ver})"
- FOUND_NPM=1
- fi
- fi
- done
- done
- [ "$FOUND_NPM" -eq 0 ] && log_clean "npm packages"
- echo ""
- # ============================================================================
- # 4. RUNNING PROCESSES
- # ============================================================================
- echo "--- [4/9] Suspicious Processes ---"
- FOUND_PROC=0
- # RAT binary names and IOC strings in process list
- PROC_PATTERNS=(
- "com.apple.act.mond"
- "ld\.py"
- "stage2\.ps1"
- "6202033"
- "sfrclak"
- "plain-crypto-js"
- )
- PS_OUTPUT=$(ps aux 2>/dev/null || ps -ef 2>/dev/null)
- for pattern in "${PROC_PATTERNS[@]}"; do
- # Use grep -E for consistent regex behavior on Linux/macOS
- MATCHES=$(echo "$PS_OUTPUT" | grep -Ei "$pattern" | grep -v "grep" | grep -v "axios_ioc_hunt" || true)
- if [ -n "$MATCHES" ]; then
- log_finding "PROCESS" "Pattern '${pattern}' found in running processes:"
- echo "$MATCHES" | while read -r line; do printf " %s\n" "$line"; done
- FOUND_PROC=1
- fi
- done
- [ "$FOUND_PROC" -eq 0 ] && log_clean "Running processes"
- echo ""
- # ============================================================================
- # 5. macOS CODESIGN ABUSE (Wiz Research)
- # The Mach-O RAT self-signs injected payloads via codesign.
- # ============================================================================
- echo "--- [5/9] macOS Codesign Abuse ---"
- FOUND_CODESIGN=0
- if [ "$OS" = "macos" ]; then
- # Check if the RAT binary exists and inspect its signature
- if [ -f "/Library/Caches/com.apple.act.mond" ]; then
- CODESIGN_INFO=$(codesign -dvvv /Library/Caches/com.apple.act.mond 2>&1 || true)
- log_finding "CODESIGN" "RAT binary signature info:"
- echo "$CODESIGN_INFO" | head -20
- FOUND_CODESIGN=1
- fi
- # Look for ad-hoc signed or unsigned executables in /Library/Caches
- # (RAT injects and self-signs payloads here)
- ADHOC_BINS=$(find /Library/Caches -type f -perm +111 2>/dev/null | while read -r bin; do
- sig=$(codesign -d --verbose=2 "$bin" 2>&1 || true)
- if echo "$sig" | grep -q "adhoc\|not signed\|invalid"; then
- echo "$bin"
- fi
- done || true)
- if [ -n "$ADHOC_BINS" ]; then
- log_finding "CODESIGN" "Suspicious ad-hoc/unsigned executables in /Library/Caches:"
- echo "$ADHOC_BINS" | while read -r line; do echo " $line"; done
- FOUND_CODESIGN=1
- fi
- # Check for recent codesign invocations in unified log (last 1 hour)
- CODESIGN_LOG=$(log show --predicate 'process == "codesign"' --last 1h 2>/dev/null | grep -i "Library/Caches" || true)
- if [ -n "$CODESIGN_LOG" ]; then
- log_finding "CODESIGN" "Recent codesign activity targeting /Library/Caches:"
- echo "$CODESIGN_LOG" | head -10
- FOUND_CODESIGN=1
- fi
- else
- echo "[SKIP] macOS-only check — skipping on ${OS}"
- fi
- [ "$FOUND_CODESIGN" -eq 0 ] && [ "$OS" = "macos" ] && log_clean "Codesign abuse"
- echo ""
- # ============================================================================
- # 6. NETWORK CONNECTIONS
- # ============================================================================
- echo "--- [6/9] Network Connections (C2: 142.11.206.73 / sfrclak.com:8000) ---"
- FOUND_NET=0
- # Check for connections to C2 IP
- if command -v lsof &>/dev/null; then
- C2_LSOF=$(lsof -i @142.11.206.73 2>/dev/null || true)
- if [ -n "$C2_LSOF" ]; then
- log_finding "NETWORK" "Active connection to C2 IP 142.11.206.73:"
- echo "$C2_LSOF"
- FOUND_NET=1
- fi
- # Also check port 8000 for any suspicious outbound
- PORT_8000=$(lsof -i :8000 2>/dev/null | grep -v "LISTEN" || true)
- if echo "$PORT_8000" | grep -q "142.11.206.73"; then
- log_finding "NETWORK" "Connection on port 8000 to C2 IP:"
- echo "$PORT_8000" | grep "142.11.206.73"
- FOUND_NET=1
- fi
- fi
- # Netstat fallback
- if command -v netstat &>/dev/null; then
- NS_MATCH=$(netstat -an 2>/dev/null | grep "142.11.206.73" || true)
- if [ -n "$NS_MATCH" ]; then
- log_finding "NETWORK" "netstat hit for C2 IP 142.11.206.73:"
- echo "$NS_MATCH"
- FOUND_NET=1
- fi
- fi
- # ss fallback (Linux)
- if [ "$OS" = "linux" ] && command -v ss &>/dev/null; then
- SS_MATCH=$(ss -tunap 2>/dev/null | grep "142.11.206.73" || true)
- if [ -n "$SS_MATCH" ]; then
- log_finding "NETWORK" "ss hit for C2 IP 142.11.206.73:"
- echo "$SS_MATCH"
- FOUND_NET=1
- fi
- fi
- [ "$FOUND_NET" -eq 0 ] && log_clean "Network connections"
- echo ""
- # ============================================================================
- # 7. DNS RESOLUTION CHECK
- # ============================================================================
- echo "--- [7/9] DNS Resolution ---"
- FOUND_DNS=0
- for domain in "sfrclak.com" "packages.npm.org"; do
- HOSTS_MATCH=$(grep -i "$domain" /etc/hosts 2>/dev/null || true)
- if [ -n "$HOSTS_MATCH" ]; then
- log_finding "DNS" "C2-related entry in /etc/hosts: ${HOSTS_MATCH}"
- FOUND_DNS=1
- fi
- done
- # macOS DNS cache check
- if [ "$OS" = "macos" ]; then
- SCUTIL_CHECK=$(scutil --dns 2>/dev/null | grep -i "sfrclak" || true)
- if [ -n "$SCUTIL_CHECK" ]; then
- log_finding "DNS" "sfrclak.com found in macOS DNS config"
- FOUND_DNS=1
- fi
- fi
- # Linux: check systemd-resolved cache if available
- if [ "$OS" = "linux" ] && command -v resolvectl &>/dev/null; then
- # Only flag if it actually resolves (contains an IP address)
- RESOLVED_CHECK=$(resolvectl query sfrclak.com 2>&1 || true)
- if echo "$RESOLVED_CHECK" | grep -qE "([0-9]{1,3}\.){3}[0-9]{1,3}"; then
- log_finding "DNS" "sfrclak.com resolved to an active IP via systemd-resolved"
- FOUND_DNS=1
- fi
- fi
- [ "$FOUND_DNS" -eq 0 ] && log_clean "DNS"
- echo ""
- # ============================================================================
- # 8. PERSISTENCE MECHANISMS
- # ============================================================================
- echo "--- [8/9] Persistence ---"
- FOUND_PERSIST=0
- IOC_STRINGS="act\.mond\|6202033\|sfrclak\|ld\.py\|wt\.exe\|plain-crypto-js\|stage2\.ps1\|MicrosoftUpdate"
- # macOS: LaunchDaemons / LaunchAgents
- if [ "$OS" = "macos" ]; then
- for plist_dir in \
- /Library/LaunchDaemons \
- /Library/LaunchAgents \
- ~/Library/LaunchAgents; do
- if [ -d "$plist_dir" ]; then
- PLIST_HITS=$(grep -rl "$IOC_STRINGS" "$plist_dir" 2>/dev/null || true)
- if [ -n "$PLIST_HITS" ]; then
- log_finding "PERSIST" "IOC string found in plist(s): ${PLIST_HITS}"
- FOUND_PERSIST=1
- fi
- fi
- done
- fi
- # Linux/macOS: crontab
- CRON_HIT=$(crontab -l 2>/dev/null | grep -E "ld\.py|6202033|sfrclak|act\.mond|stage2\.ps1" || true)
- if [ -n "$CRON_HIT" ]; then
- log_finding "PERSIST" "IOC string found in user crontab: ${CRON_HIT}"
- FOUND_PERSIST=1
- fi
- # System crontabs
- for cron_file in /etc/crontab /etc/cron.d/*; do
- if [ -f "$cron_file" ]; then
- SYS_CRON=$(grep -E "ld\.py|6202033|sfrclak|act\.mond|stage2\.ps1" "$cron_file" 2>/dev/null || true)
- if [ -n "$SYS_CRON" ]; then
- log_finding "PERSIST" "IOC in system cron ${cron_file}: ${SYS_CRON}"
- FOUND_PERSIST=1
- fi
- fi
- done
- # Linux: systemd units
- if [ "$OS" = "linux" ]; then
- for unit_dir in /etc/systemd/system /usr/lib/systemd/system ~/.config/systemd/user; do
- if [ -d "$unit_dir" ]; then
- UNIT_HITS=$(grep -rl "ld\.py\|6202033\|sfrclak\|stage2" "$unit_dir" 2>/dev/null || true)
- if [ -n "$UNIT_HITS" ]; then
- log_finding "PERSIST" "IOC in systemd unit(s): ${UNIT_HITS}"
- FOUND_PERSIST=1
- fi
- fi
- done
- fi
- [ "$FOUND_PERSIST" -eq 0 ] && log_clean "Persistence mechanisms"
- echo ""
- # ============================================================================
- # 9. SUMMARY
- # ============================================================================
- echo "--- [9/9] Summary ---"
- echo ""
- if [ "$COMPROMISED" -eq 1 ]; then
- echo "============================================================"
- echo " RESULT: INDICATORS OF COMPROMISE DETECTED"
- echo "============================================================"
- echo ""
- echo "Findings:"
- printf "%b\n" "$FINDINGS"
- echo ""
- echo "Wiz Research SHA256 reference hashes:"
- echo " macOS RAT: ${HASH_MACOS_RAT}"
- echo " Win stage2: ${HASH_WIN_STAGE2}"
- echo " Linux RAT: ${HASH_LINUX_RAT}"
- echo " Dropper pkg: ${HASH_DROPPER}"
- echo " axios 0.30.4: ${HASH_AXIOS_0304}"
- echo " axios 1.14.1: ${HASH_AXIOS_1141}"
- echo ""
- echo "Recommended actions:"
- echo " 1. Isolate this host from the network immediately"
- echo " 2. Preserve forensic evidence (memory dump, disk image)"
- echo " 3. Rotate all credentials/tokens accessible from this host"
- echo " (Wiz: assume credential compromise if malicious packages executed)"
- echo " 4. Check npm cache: npm cache ls 2>/dev/null | grep -E 'axios|plain-crypto'"
- echo " 5. Review git history for lockfile changes in the exposure window"
- echo " (2026-03-31 00:21 UTC to 03:15 UTC)"
- echo " 6. Scan for secrets in env vars, .env files, API keys, tokens"
- echo " (RAT transmits system inventory to C2 every 60s)"
- echo ""
- exit 1
- else
- echo "============================================================"
- echo " RESULT: CLEAN — No Axios compromise indicators found"
- echo "============================================================"
- echo ""
- exit 0
- fi
Advertisement