|
<# |
|
.SYNOPSIS |
|
Checks if this machine or project is impacted by the axios npm supply chain compromise (2026-03-31). |
|
.DESCRIPTION |
|
Scans for RAT artifacts, malicious dependencies, and compromised lockfile entries |
|
related to axios@1.14.1 / axios@0.30.4 / plain-crypto-js@4.2.1. |
|
.PARAMETER ProjectDir |
|
Optional path to a Node.js project directory to scan. If omitted, scans common locations. |
|
.LINK |
|
https://github.com/axios/axios/issues/10604 |
|
https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan |
|
#> |
|
param( |
|
[string]$ProjectDir |
|
) |
|
|
|
$compromised = $false |
|
|
|
function Write-Finding { |
|
param([string]$Message) |
|
Write-Host "[!] $Message" -ForegroundColor Red |
|
$script:compromised = $true |
|
} |
|
|
|
function Write-Clean { |
|
param([string]$Message) |
|
Write-Host "[OK] $Message" -ForegroundColor Green |
|
} |
|
|
|
function Write-Info { |
|
param([string]$Message) |
|
Write-Host "[*] $Message" -ForegroundColor Cyan |
|
} |
|
|
|
Write-Host "" |
|
Write-Host "=== axios Supply Chain Compromise Check (2026-03-31) ===" -ForegroundColor Yellow |
|
Write-Host "Checking for: axios@1.14.1, axios@0.30.4, plain-crypto-js@4.2.1" |
|
Write-Host "Reference: https://github.com/axios/axios/issues/10604" |
|
Write-Host "" |
|
|
|
# --- 1. Check for Windows RAT artifacts --- |
|
Write-Host "--- 1. Checking for RAT artifacts ---" -ForegroundColor Yellow |
|
|
|
$wtExePath = Join-Path $env:ProgramData "wt.exe" |
|
if (Test-Path $wtExePath) { |
|
Write-Finding "RAT artifact found: $wtExePath (PowerShell binary planted by dropper)" |
|
} else { |
|
Write-Clean "No planted wt.exe in ProgramData" |
|
} |
|
|
|
$tempVbs = Join-Path $env:TEMP "6202033.vbs" |
|
if (Test-Path $tempVbs) { |
|
Write-Finding "RAT dropper script found: $tempVbs" |
|
} else { |
|
Write-Clean "No dropper VBS in TEMP" |
|
} |
|
|
|
$tempPs1 = Join-Path $env:TEMP "6202033.ps1" |
|
if (Test-Path $tempPs1) { |
|
Write-Finding "RAT PowerShell payload found: $tempPs1" |
|
} else { |
|
Write-Clean "No dropper PS1 in TEMP" |
|
} |
|
|
|
Write-Host "" |
|
|
|
# --- 2. Check DNS / network indicators --- |
|
Write-Host "--- 2. Checking network indicators ---" -ForegroundColor Yellow |
|
|
|
$hostsFile = Join-Path $env:SystemRoot "System32\drivers\etc\hosts" |
|
if (Test-Path $hostsFile) { |
|
$hostsContent = Get-Content $hostsFile -Raw |
|
if ($hostsContent -match "sfrclak\.com") { |
|
Write-Info "C2 domain 'sfrclak.com' found in hosts file (may be a block entry ΓÇö verify manually)" |
|
} |
|
} |
|
|
|
try { |
|
$dnsResult = Resolve-DnsName -Name "sfrclak.com" -ErrorAction Stop -DnsOnly 2>$null |
|
$c2Ip = $dnsResult | Where-Object { $_.IPAddress -eq "142.11.206.73" } |
|
if ($c2Ip) { |
|
Write-Info "C2 domain sfrclak.com resolves to known malicious IP 142.11.206.73 ΓÇö domain is still active" |
|
} else { |
|
Write-Info "C2 domain sfrclak.com resolves but NOT to known C2 IP (may have been sinkholed)" |
|
} |
|
} catch { |
|
Write-Clean "C2 domain sfrclak.com does not resolve (likely taken down or blocked)" |
|
} |
|
|
|
Write-Host "" |
|
|
|
# --- 3. Scan project directories --- |
|
Write-Host "--- 3. Scanning for compromised dependencies ---" -ForegroundColor Yellow |
|
|
|
$dirsToScan = @() |
|
|
|
if ($ProjectDir) { |
|
if (Test-Path $ProjectDir) { |
|
$dirsToScan += $ProjectDir |
|
} else { |
|
Write-Host "Specified project directory not found: $ProjectDir" -ForegroundColor Red |
|
} |
|
} else { |
|
# Scan current directory |
|
$dirsToScan += (Get-Location).Path |
|
Write-Info "No -ProjectDir specified. Scanning current directory: $($dirsToScan[0])" |
|
} |
|
|
|
foreach ($dir in $dirsToScan) { |
|
Write-Info "Scanning: $dir" |
|
|
|
# Check for plain-crypto-js in node_modules (any depth) |
|
$plainCryptoJsPaths = Get-ChildItem -Path $dir -Filter "plain-crypto-js" -Directory -Recurse -ErrorAction SilentlyContinue | |
|
Where-Object { $_.FullName -match "node_modules" } |
|
|
|
if ($plainCryptoJsPaths) { |
|
foreach ($p in $plainCryptoJsPaths) { |
|
Write-Finding "Malicious dependency found: $($p.FullName)" |
|
Write-Finding " -> The dropper likely already executed. Treat this system as compromised." |
|
} |
|
} else { |
|
Write-Clean "No plain-crypto-js found in node_modules" |
|
} |
|
|
|
# Check package-lock.json files for compromised versions |
|
$lockFiles = Get-ChildItem -Path $dir -Filter "package-lock.json" -Recurse -ErrorAction SilentlyContinue | |
|
Where-Object { $_.FullName -notmatch "node_modules" } |
|
|
|
foreach ($lockFile in $lockFiles) { |
|
$content = Get-Content $lockFile.FullName -Raw -ErrorAction SilentlyContinue |
|
if (-not $content) { continue } |
|
|
|
# Match the npm registry resolved URL for axios specifically (e.g. "resolved": ".../axios/-/axios-1.14.1.tgz") |
|
# Using the registry path "axios/-/axios-X.Y.Z.tgz" avoids false positives from other packages |
|
# that happen to share the same version number (e.g. dashdash@1.14.1, tslib@1.14.1). |
|
$hasCompromisedAxios = $content -match 'axios/-/axios-1\.14\.1\.tgz' -or |
|
$content -match 'axios/-/axios-0\.30\.4\.tgz' |
|
|
|
$hasPlainCryptoJs = $content -match 'plain-crypto-js' |
|
|
|
if ($hasCompromisedAxios) { |
|
Write-Finding "Compromised axios version in lockfile: $($lockFile.FullName)" |
|
} |
|
|
|
if ($hasPlainCryptoJs) { |
|
Write-Finding "Malicious plain-crypto-js referenced in lockfile: $($lockFile.FullName)" |
|
} |
|
|
|
if (-not $hasCompromisedAxios -and -not $hasPlainCryptoJs) { |
|
Write-Clean "Lockfile clean: $($lockFile.FullName)" |
|
} |
|
} |
|
|
|
if (-not $lockFiles) { |
|
Write-Info "No package-lock.json files found in $dir (outside node_modules)" |
|
} |
|
} |
|
|
|
Write-Host "" |
|
|
|
# --- Summary --- |
|
Write-Host "--- Summary ---" -ForegroundColor Yellow |
|
if ($compromised) { |
|
Write-Host "" |
|
Write-Finding "POTENTIAL COMPROMISE DETECTED" |
|
Write-Host "" |
|
Write-Host "Recommended actions:" -ForegroundColor Red |
|
Write-Host " 1. Do NOT clean in place ΓÇö rebuild from a known-good state" -ForegroundColor Red |
|
Write-Host " 2. Rotate ALL credentials (npm tokens, SSH keys, cloud creds, .env files)" -ForegroundColor Red |
|
Write-Host " 3. Audit CI/CD pipelines for affected npm install runs" -ForegroundColor Red |
|
Write-Host " 4. Pin axios to 1.14.0 or 0.30.3" -ForegroundColor Red |
|
Write-Host " 5. Block C2: 0.0.0.0 sfrclak.com in hosts, firewall 142.11.206.73" -ForegroundColor Red |
|
} else { |
|
Write-Host "" |
|
Write-Clean "No indicators of compromise found." |
|
Write-Host "" |
|
Write-Host "Stay safe:" -ForegroundColor Green |
|
Write-Host " - Pin axios to a known-good version (1.14.0 or 1.13.5)" -ForegroundColor Green |
|
Write-Host " - Use 'npm ci' instead of 'npm install'" -ForegroundColor Green |
|
Write-Host " - Consider using --ignore-scripts in CI/CD" -ForegroundColor Green |
|
} |
|
Write-Host "" |
This condition is false
the first branch
grep -qE '"axios-(1\.14\.1|0\.30\.4)\.tgz"' "$lockfile" 2>/dev/nullis correct, but the second one is not, as it just checks that the branch doesn't contain 0.30.4 or 1.14.1 but for any package.using jq improve the situation