Last active
March 31, 2026 08:56
-
-
Save brysonreece/a248c1203219eeb980da26ceb11f4c9d to your computer and use it in GitHub Desktop.
Helper scripts to scan your system for compromised Axios versions and plain-crypto-js/RAT artifacts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env pwsh | |
| # axios RAT Scanner — March 31 2026 incident | |
| # Affected: axios@1.14.1, axios@0.30.4 (via plain-crypto-js@4.2.1) | |
| # Ref: https://socket.dev/blog/axios-npm-package-compromised | |
| # Compatible with Windows PowerShell 5.1+ and PowerShell Core 7+ | |
| #Requires -Version 5.1 | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'SilentlyContinue' | |
| # ─── Helpers ───────────────────────────────────────────────────────────────── | |
| function Write-Color { | |
| param([string]$Text, [ConsoleColor]$Color = 'White', [switch]$NoNewline) | |
| $prev = $Host.UI.RawUI.ForegroundColor | |
| $Host.UI.RawUI.ForegroundColor = $Color | |
| if ($NoNewline) { Write-Host $Text -NoNewline } else { Write-Host $Text } | |
| $Host.UI.RawUI.ForegroundColor = $prev | |
| } | |
| function Write-Status { | |
| param([string]$Text) | |
| Write-Host "`r$((' ' * 90))`r" -NoNewline | |
| Write-Color " scanning: $($Text.Substring(0, [Math]::Min(80, $Text.Length)))" -Color DarkGray -NoNewline | |
| } | |
| function Clear-StatusLine { | |
| Write-Host "`r$((' ' * 90))`r" -NoNewline | |
| } | |
| function Write-Rule { | |
| Write-Color ('─' * 70) -Color DarkGray | |
| } | |
| # ─── Scan roots ────────────────────────────────────────────────────────────── | |
| # Cover all drives on Windows; fall back to filesystem root on other platforms. | |
| function Get-ScanRoots { | |
| if ($IsWindows -or ($PSVersionTable.PSVersion.Major -le 5)) { | |
| Get-PSDrive -PSProvider FileSystem | Select-Object -ExpandProperty Root | |
| } else { | |
| @('/') | |
| } | |
| } | |
| $foundCount = 0 | |
| $pcjsCount = 0 | |
| $ratCount = 0 | |
| $findings = [System.Collections.Generic.List[string]]::new() | |
| # ─── Phase 1: Poisoned axios versions ──────────────────────────────────────── | |
| Write-Host "" | |
| Write-Rule | |
| Write-Color " [1/3] Scanning node_modules for poisoned axios versions..." -Color Cyan | |
| Write-Color " Targets: axios@1.14.1 | axios@0.30.4" -Color DarkGray | |
| Write-Rule | |
| Write-Host "" | |
| $scanRoots = Get-ScanRoots | |
| foreach ($root in $scanRoots) { | |
| Get-ChildItem -Path $root -Recurse -Filter 'package.json' -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -match '\\node_modules\\axios\\package\.json$|/node_modules/axios/package\.json$' } | | |
| ForEach-Object { | |
| $pkgPath = $_.FullName | |
| Write-Status $pkgPath | |
| $content = Get-Content $pkgPath -Raw -ErrorAction SilentlyContinue | |
| if ($content -match '"version"\s*:\s*"(1\.14\.1|0\.30\.4)"') { | |
| $ver = $Matches[1] | |
| $foundCount++ | |
| Clear-StatusLine | |
| [console]::Beep(880, 300) | |
| Write-Color " !! POISONED axios@$ver found" -Color Red | |
| Write-Color " Path: " -Color Yellow -NoNewline | |
| Write-Host ($pkgPath -replace '[/\\]package\.json$', '') | |
| Write-Host "" | |
| $findings.Add("Poisoned axios@$ver at: $($pkgPath -replace '[/\\]package\.json$','')") | |
| } | |
| } | |
| } | |
| Clear-StatusLine | |
| # ─── Phase 2: plain-crypto-js dropper artifact ─────────────────────────────── | |
| Write-Rule | |
| Write-Color " [2/3] Scanning for plain-crypto-js dropper artifact..." -Color Cyan | |
| Write-Color " Folder presence = dropper executed, even if self-deleted" -Color DarkGray | |
| Write-Rule | |
| Write-Host "" | |
| foreach ($root in $scanRoots) { | |
| Get-ChildItem -Path $root -Recurse -Directory -Filter 'plain-crypto-js' -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -match '\\node_modules\\plain-crypto-js$|/node_modules/plain-crypto-js$' } | | |
| ForEach-Object { | |
| $dir = $_.FullName | |
| $pcjsCount++ | |
| Clear-StatusLine | |
| [console]::Beep(880, 300) | |
| Write-Color " !! DROPPER ARTIFACT: plain-crypto-js found" -Color Red | |
| Write-Color " Path: " -Color Yellow -NoNewline | |
| Write-Host $dir | |
| $pkgJson = Join-Path $dir 'package.json' | |
| if (-not (Test-Path $pkgJson)) { | |
| Write-Color " ⚠ package.json missing — dropper likely already executed" -Color Red | |
| } else { | |
| $pcjsContent = Get-Content $pkgJson -Raw -ErrorAction SilentlyContinue | |
| if ($pcjsContent -match '"version"\s*:\s*"([^"]+)"') { | |
| Write-Color " Version: " -Color Yellow -NoNewline | |
| Write-Host $Matches[1] | |
| } | |
| } | |
| Write-Host "" | |
| $findings.Add("plain-crypto-js artifact at: $dir") | |
| } | |
| } | |
| Clear-StatusLine | |
| # ─── Phase 3: OS-level RAT artifacts ───────────────────────────────────────── | |
| Write-Rule | |
| Write-Color " [3/3] Checking for OS-level RAT artifacts..." -Color Cyan | |
| Write-Rule | |
| Write-Host "" | |
| function Test-RatArtifact { | |
| param([string]$Label, [string]$ArtifactPath) | |
| $resolved = [System.Environment]::ExpandEnvironmentVariables($ArtifactPath) | |
| if (Test-Path $resolved) { | |
| $ratCount++ | |
| [console]::Beep(1200, 500) | |
| Write-Color " !! RAT ARTIFACT FOUND ($Label)" -Color Red | |
| Write-Color " Path: " -Color Yellow -NoNewline | |
| Write-Host $resolved | |
| Write-Host "" | |
| $script:findings.Add("RAT artifact ($Label) at: $resolved") | |
| } else { | |
| Write-Color " $Label path not present: $resolved" -Color DarkGray | |
| } | |
| } | |
| $isWin = $IsWindows -or ($PSVersionTable.PSVersion.Major -le 5) | |
| $isMac = $IsMacOS | |
| # Linux also covers WSL — check both Linux and Windows paths under WSL | |
| $isWSL = $IsLinux -and (Test-Path '/proc/sys/fs/binfmt_misc/WSLInterop' -ErrorAction SilentlyContinue) | |
| if ($isWin) { | |
| Test-RatArtifact 'Windows' '%PROGRAMDATA%\wt.exe' | |
| } elseif ($isMac) { | |
| Test-RatArtifact 'macOS' '/Library/Caches/com.apple.act.mond' | |
| } else { | |
| Test-RatArtifact 'Linux' '/tmp/ld.py' | |
| if ($isWSL) { | |
| $winPD = cmd.exe /c 'echo %PROGRAMDATA%' 2>$null | |
| if ($winPD) { | |
| Test-RatArtifact 'Windows (via WSL)' "$($winPD.Trim())\wt.exe" | |
| } | |
| } | |
| } | |
| Write-Host "" | |
| # ─── Summary ───────────────────────────────────────────────────────────────── | |
| Write-Rule | |
| if ($foundCount -gt 0 -or $pcjsCount -gt 0 -or $ratCount -gt 0) { | |
| [console]::Beep(1200, 200); Start-Sleep -Milliseconds 50 | |
| [console]::Beep(1200, 200); Start-Sleep -Milliseconds 50 | |
| [console]::Beep(1200, 400) | |
| Write-Color " SYSTEM MAY BE COMPROMISED — TAKE IMMEDIATE ACTION" -Color Red | |
| Write-Host "" | |
| Write-Color " Findings:" -Color Yellow | |
| if ($foundCount -gt 0) { Write-Host " • $foundCount poisoned axios install(s)" } | |
| if ($pcjsCount -gt 0) { Write-Host " • $pcjsCount plain-crypto-js artifact(s)" } | |
| if ($ratCount -gt 0) { Write-Host " • $ratCount OS-level RAT artifact(s)" } | |
| Write-Host "" | |
| Write-Color " Required actions:" -Color Yellow | |
| Write-Host " 1. Treat this machine as fully compromised — do not clean in place" | |
| Write-Host " 2. Rotate ALL credentials: npm tokens, SSH keys, AWS/GCP/Azure, .env secrets" | |
| Write-Host " 3. Downgrade axios: npm install axios@1.14.0 (or @0.30.3 for 0.x)" | |
| Write-Host " 4. Remove plain-crypto-js from any node_modules" | |
| Write-Host " 5. Audit CI/CD pipeline runs during the March 31 2026 exposure window" | |
| Write-Host " 6. Block C2 egress — sfrclak.com:8000" | |
| Write-Host "" | |
| Write-Color " Block via Windows Firewall (run as Administrator):" -Color Yellow | |
| Write-Host " New-NetFirewallRule -DisplayName 'Block axios C2' ``" | |
| Write-Host " -Direction Outbound -Action Block ``" | |
| Write-Host " -RemoteAddress 142.11.206.73 -Protocol TCP" | |
| Write-Host "" | |
| Write-Color " Block via hosts file (run as Administrator):" -Color Yellow | |
| Write-Host " Add-Content C:\Windows\System32\drivers\etc\hosts '0.0.0.0 sfrclak.com'" | |
| } else { | |
| Write-Color " Scan complete — no poisoned packages or RAT artifacts found" -Color Green | |
| } | |
| Write-Rule | |
| Write-Host "" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # axios RAT Scanner — March 31 2026 incident | |
| # Affected: axios@1.14.1, axios@0.30.4 (via plain-crypto-js@4.2.1) | |
| # Ref: https://socket.dev/blog/axios-npm-package-compromised | |
| RED='\033[1;31m' | |
| YEL='\033[1;33m' | |
| GRN='\033[1;32m' | |
| CYN='\033[1;36m' | |
| DIM='\033[2m' | |
| RST='\033[0m' | |
| BELL='\a' | |
| found_count=0 | |
| pcjs_count=0 | |
| rat_count=0 | |
| # ─── Phase 1: Scan for poisoned axios versions ─────────────────────────────── | |
| printf "\n${CYN}[1/3] Scanning node_modules for poisoned axios versions...${RST}\n" | |
| printf "${DIM} Targets: axios@1.14.1, axios@0.30.4${RST}\n\n" | |
| while IFS= read -r f; do | |
| v=$(grep -oE '"version"\s*:\s*"(1\.14\.1|0\.30\.4)"' "$f" 2>/dev/null \ | |
| | grep -oE '(1\.14\.1|0\.30\.4)') | |
| if [[ -n "$v" ]]; then | |
| (( found_count++ )) | |
| printf "${BELL}${RED} POISONED axios@%s found${RST}\n" "$v" | |
| printf " ${YEL}Path:${RST} %s\n\n" "${f%/package.json}" | |
| else | |
| printf "\r${DIM}scanning: %.80s${RST}\033[K" "$f" | |
| fi | |
| done < <(find / -path '*/node_modules/axios/package.json' -type f 2>/dev/null) | |
| printf "\r\033[K" | |
| # ─── Phase 2: Scan for plain-crypto-js (dropper artifact) ──────────────────── | |
| # The RAT self-deletes its own setup.js and package.json after running, | |
| # but the directory itself persists — its presence alone means the dropper ran. | |
| printf "${CYN}[2/3] Scanning for plain-crypto-js dropper artifact...${RST}\n" | |
| printf "${DIM} (folder presence = dropper executed, even if self-deleted)${RST}\n\n" | |
| while IFS= read -r d; do | |
| (( pcjs_count++ )) | |
| printf "${BELL}${RED} DROPPER ARTIFACT: plain-crypto-js found${RST}\n" | |
| printf " ${YEL}Path:${RST} %s\n" "$d" | |
| if [[ ! -f "$d/package.json" ]]; then | |
| printf " ${RED}⚠ package.json missing — dropper likely already executed${RST}\n\n" | |
| else | |
| pcv=$(grep -oE '"version"\s*:\s*"[^"]+"' "$d/package.json" 2>/dev/null \ | |
| | grep -oE '"[^"]+"\s*$' | tr -d '"') | |
| printf " ${YEL}Version:${RST} %s\n\n" "$pcv" | |
| fi | |
| done < <(find / -path '*/node_modules/plain-crypto-js' -type d 2>/dev/null) | |
| printf "\r\033[K" | |
| # ─── Phase 3: Check for OS-level RAT artifacts ─────────────────────────────── | |
| printf "${CYN}[3/3] Checking for OS-level RAT artifacts...${RST}\n\n" | |
| check_artifact() { | |
| local label="$1" path="$2" | |
| if [[ -e "$path" ]]; then | |
| (( rat_count++ )) | |
| printf "${BELL}${RED} RAT ARTIFACT FOUND (%s)${RST}\n" "$label" | |
| printf " ${YEL}Path:${RST} %s\n\n" "$path" | |
| else | |
| printf " ${DIM}%s path not present: %s${RST}\n" "$label" "$path" | |
| fi | |
| } | |
| case "$(uname -s)" in | |
| Darwin) check_artifact "macOS" "/Library/Caches/com.apple.act.mond" ;; | |
| Linux) check_artifact "Linux" "/tmp/ld.py" ;; | |
| MINGW*|MSYS*|CYGWIN*) | |
| check_artifact "Windows" "${PROGRAMDATA}\\wt.exe" ;; | |
| *) | |
| # WSL: check both Linux and Windows paths | |
| check_artifact "Linux" "/tmp/ld.py" | |
| win_pd=$(cmd.exe /c "echo %PROGRAMDATA%" 2>/dev/null | tr -d '\r') | |
| [[ -n "$win_pd" ]] && check_artifact "Windows (via WSL)" "${win_pd}\\wt.exe" | |
| ;; | |
| esac | |
| # ─── Summary ───────────────────────────────────────────────────────────────── | |
| printf "\n${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RST}\n" | |
| if (( found_count > 0 || pcjs_count > 0 || rat_count > 0 )); then | |
| printf "${BELL}${RED} SYSTEM MAY BE COMPROMISED — TAKE IMMEDIATE ACTION${RST}\n\n" | |
| printf "${YEL}Findings:${RST}\n" | |
| (( found_count > 0 )) && printf " • %d poisoned axios install(s)\n" "$found_count" | |
| (( pcjs_count > 0 )) && printf " • %d plain-crypto-js artifact(s)\n" "$pcjs_count" | |
| (( rat_count > 0 )) && printf " • %d OS-level RAT artifact(s)\n" "$rat_count" | |
| printf "\n${YEL}Required actions:${RST}\n" | |
| printf " 1. Treat this machine as fully compromised — do not clean in place\n" | |
| printf " 2. Rotate ALL credentials: npm tokens, SSH keys, AWS/GCP/Azure, .env secrets\n" | |
| printf " 3. Downgrade axios: npm install axios@1.14.0 (or @0.30.3 for 0.x)\n" | |
| printf " 4. Remove plain-crypto-js from any node_modules\n" | |
| printf " 5. Audit CI/CD pipeline runs during the March 31 2026 exposure window\n" | |
| printf " 6. Block C2 egress — sfrclak.com:8000\n" | |
| else | |
| printf "${GRN} scan complete — no poisoned packages or RAT artifacts found${RST}\n" | |
| fi | |
| printf "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RST}\n\n" |
Comments are disabled for this gist.