Skip to content

Instantly share code, notes, and snippets.

@Fazzani
Last active May 19, 2026 13:51
Show Gist options
  • Select an option

  • Save Fazzani/f2627d3f340f16643f41c7cab7a6b772 to your computer and use it in GitHub Desktop.

Select an option

Save Fazzani/f2627d3f340f16643f41c7cab7a6b772 to your computer and use it in GitHub Desktop.
CTI Advisory #002 — CVE-2026-45321 — TLP:AMBER # Threat actor: TeamPCP (DeadCatx3 / PCPcat / ShellForce / CipherForce) # CVSS 9.6 Critical
# ============================================================
# Invoke-MiniShaiHuludCheck.ps1
# CTI Advisory #002 — CVE-2026-45321 — TLP:AMBER
# Threat actor: TeamPCP (DeadCatx3 / PCPcat / ShellForce / CipherForce)
# CVSS 9.6 Critical
#
# Validates whether REAL artefacts are present on disk —
# not just traces in PowerShell history.
#
# Designed to run via one-liner (Gist):
# Windows (PowerShell): irm <gist_raw_url> | iex
# macOS / Linux: curl -fsSL <gist_raw_url> | pwsh
#
# ⚠ CRITICAL SAFETY ORDER:
# ISOLATE machine → IMAGE → KILL DAEMON → REVOKE secrets → ROTATE
# DO NOT revoke tokens before network isolation.
# The worm watchdog triggers data wipe on token revocation.
# ============================================================
Set-StrictMode -Version Latest
# ── Helpers ─────────────────────────────────────────────────────────────────
function Write-Header {
param([string]$Text)
Write-Host "`n═══════════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host " $Text" -ForegroundColor Cyan
Write-Host "═══════════════════════════════════════════════════════" -ForegroundColor Cyan
}
function Write-Section {
param([string]$Text)
Write-Host "`n── $Text ─────────────────────────────────────────────" -ForegroundColor DarkCyan
}
function Write-Hit {
param([string]$Severity, [string]$Label, [string]$Detail)
$color = switch ($Severity) {
'critical' { 'Red' }
'high' { 'Yellow' }
default { 'Magenta' }
}
Write-Host " [$(($Severity).ToUpper())] $Label" -ForegroundColor $color
if ($Detail) {
Write-Host " → $Detail" -ForegroundColor DarkGray
}
}
function Write-OK {
param([string]$Text)
Write-Host " [OK] $Text" -ForegroundColor Green
}
# ── IOC Definitions ──────────────────────────────────────────────────────────
$PAYLOAD_FILES = @(
@{ Name = 'setup_bun.js'; Severity = 'critical'; Hash = $null }
@{ Name = 'bun_environment.js'; Severity = 'critical'; Hash = $null }
@{ Name = 'router_init.js'; Severity = 'critical'; Hash = 'ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c' }
@{ Name = 'router_runtime.js'; Severity = 'critical'; Hash = $null }
@{ Name = 'tanstack_runner.js'; Severity = 'critical'; Hash = '2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96' }
)
$PERSISTENCE_PATHS = @(
@{ RelPath = '.claude\settings.json'; Severity = 'critical'; Label = '.claude/settings.json (modified by worm)' }
@{ RelPath = '.claude\setup.mjs'; Severity = 'critical'; Label = '.claude/setup.mjs (worm dropper)' }
@{ RelPath = '.claude.json'; Severity = 'critical'; Label = '.claude.json (harvested by worm)' }
@{ RelPath = '.vscode\tasks.json'; Severity = 'critical'; Label = '.vscode/tasks.json (modified by worm)' }
@{ RelPath = '.vscode\setup.mjs'; Severity = 'critical'; Label = '.vscode/setup.mjs (worm dropper)' }
)
$SUSPICIOUS_PROCESSES = @(
@{ Pattern = 'gh-token-monitor'; Severity = 'critical'; Label = 'gh-token-monitor daemon (persistence)' }
@{ Pattern = 'tanstack'; Severity = 'critical'; Label = 'tanstack_runner process' }
@{ Pattern = 'router_runtime'; Severity = 'critical'; Label = 'router_runtime process' }
)
$C2_DOMAINS = @(
'git-tanstack.com'
'seed1.getsession.org'
'zero.masscan.cloud'
)
$SUSPICIOUS_STRINGS = @(
@{ Pattern = 'A Mini Shai-Hulud has Appeared'; Severity = 'critical' }
@{ Pattern = 'Sha1-Hulud: The Second Coming'; Severity = 'critical' }
@{ Pattern = 'Shai-Hulud: Here We Go Again'; Severity = 'critical' }
@{ Pattern = 'IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner'; Severity = 'critical' }
@{ Pattern = 'ctf-scramble-v2'; Severity = 'critical' }
@{ Pattern = 'OhNoWhatsGoingOnWithGitHub:'; Severity = 'critical' }
@{ Pattern = 'svksjrhjkcejg'; Severity = 'critical' }
)
$COMPROMISED_PACKAGES = @(
'@mistralai/mistralai'
'@uipath/apollo-core'
'intercom-client@7.0.4'
'mbt@1.2.48'
'@cap-js/db-service'
'@cap-js/sqlite@2.2.2'
'@cap-js/postgres'
)
# ── State ────────────────────────────────────────────────────────────────────
$findings = [System.Collections.Generic.List[hashtable]]::new()
function Add-Finding {
param([string]$Severity, [string]$Category, [string]$Label, [string]$Detail = '')
$findings.Add(@{ Severity = $Severity; Category = $Category; Label = $Label; Detail = $Detail })
Write-Hit -Severity $Severity -Label $Label -Detail $Detail
}
# ── Banner ────────────────────────────────────────────────────────────────────
Write-Host @"
`n
╔══════════════════════════════════════════════════════════════╗
║ Mini Shai-Hulud — Artefact Verification ║
║ CTI Advisory #002 · CVE-2026-45321 · TLP:AMBER ║
║ v3 ║
║ ⚠ ISOLATE machine BEFORE revoking any token/secret ⚠ ║
╚══════════════════════════════════════════════════════════════╝
"@ -ForegroundColor DarkYellow
$auditDate = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
$hostname = $env:COMPUTERNAME
Write-Host " Host : $hostname" -ForegroundColor Gray
Write-Host " Date : $auditDate" -ForegroundColor Gray
Write-Host " User : $($env:USERNAME)`n" -ForegroundColor Gray
# ═══════════════════════════════════════════════════════════════════════════════
# 1. PAYLOAD FILES ON DISK
# ═══════════════════════════════════════════════════════════════════════════════
Write-Header "1/5 — Payload files on disk"
$searchRoots = @(
$env:USERPROFILE
$env:APPDATA
$env:LOCALAPPDATA
$env:TEMP
[System.IO.Path]::GetTempPath()
'C:\ProgramData'
) | Where-Object { $_ -and (Test-Path $_) } | Sort-Object -Unique
foreach ($payload in $PAYLOAD_FILES) {
$found = $false
foreach ($root in $searchRoots) {
try {
$matches_ = Get-ChildItem -Path $root -Recurse -Filter $payload.Name `
-ErrorAction SilentlyContinue -Force 2>$null
foreach ($f in $matches_) {
$found = $true
$detail = $f.FullName
# SHA-256 verification
if ($payload.Hash) {
try {
$actual = (Get-FileHash $f.FullName -Algorithm SHA256).Hash.ToLower()
if ($actual -eq $payload.Hash) {
$detail = "$($f.FullName) — HASH CONFIRMED malicious ($actual)"
} else {
$detail = "$($f.FullName) — hash mismatch (actual: $actual)"
# File exists but hash differs — still suspicious, treat as high
}
} catch { $detail = "$($f.FullName) — could not hash file" }
}
Add-Finding -Severity $payload.Severity `
-Category 'filesystem' `
-Label "$($payload.Name) found on disk" `
-Detail $detail
}
} catch { <# silently skip inaccessible dirs #> }
}
if (-not $found) {
Write-OK "$($payload.Name) — not found on disk"
}
}
# Special case: Linux payload (running via WSL or cross-platform)
if (Test-Path '/tmp/transformers.pyz' -ErrorAction SilentlyContinue) {
Add-Finding -Severity 'critical' -Category 'filesystem' `
-Label '/tmp/transformers.pyz (PyPI mistralai payload)' `
-Detail '/tmp/transformers.pyz'
} else {
Write-OK "/tmp/transformers.pyz — not found"
}
# ═══════════════════════════════════════════════════════════════════════════════
# 2. PERSISTENCE ARTEFACTS
# ═══════════════════════════════════════════════════════════════════════════════
Write-Header "2/5 — Persistence artefacts"
foreach ($p in $PERSISTENCE_PATHS) {
$fullPath = Join-Path $env:USERPROFILE $p.RelPath
if (Test-Path $fullPath -ErrorAction SilentlyContinue) {
Add-Finding -Severity $p.Severity -Category 'persistence' `
-Label $p.Label -Detail $fullPath
} else {
Write-OK "$($p.Label) — not found"
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# 3. PROCESSES
# ═══════════════════════════════════════════════════════════════════════════════
Write-Header "3/5 — Running processes"
foreach ($proc in $SUSPICIOUS_PROCESSES) {
$running = Get-Process -ErrorAction SilentlyContinue | Where-Object {
$_.ProcessName -match $proc.Pattern -or
($_.MainModule -and $_.MainModule.FileName -match $proc.Pattern)
}
if ($running) {
foreach ($r in $running) {
Add-Finding -Severity $proc.Severity -Category 'process' `
-Label $proc.Label `
-Detail "PID=$($r.Id) Name=$($r.ProcessName)"
}
} else {
Write-OK "$($proc.Label) — not running"
}
}
# gh-token-monitor via scheduled task
Write-Section "Scheduled tasks"
try {
$tasks = Get-ScheduledTask -ErrorAction SilentlyContinue |
Where-Object { $_.TaskName -match 'gh-token-monitor|tanstack|router_runtime' }
if ($tasks) {
foreach ($t in $tasks) {
Add-Finding -Severity 'critical' -Category 'persistence' `
-Label "Suspicious scheduled task: $($t.TaskName)" `
-Detail $t.TaskPath
}
} else {
Write-OK "No suspicious scheduled tasks found"
}
} catch {
Write-Host " [INFO] Could not query scheduled tasks: $_" -ForegroundColor DarkGray
}
# ═══════════════════════════════════════════════════════════════════════════════
# 4. NETWORK — Active connections to C2 domains
# ═══════════════════════════════════════════════════════════════════════════════
Write-Header "4/5 — Active network connections to C2"
$tcpConnections = Get-NetTCPConnection -ErrorAction SilentlyContinue
foreach ($domain in $C2_DOMAINS) {
try {
$resolved = [System.Net.Dns]::GetHostAddresses($domain) | Select-Object -ExpandProperty IPAddressToString
} catch { $resolved = @() }
$hit = $tcpConnections | Where-Object {
$resolved -contains $_.RemoteAddress
}
if ($hit) {
foreach ($c in $hit) {
Add-Finding -Severity 'critical' -Category 'network' `
-Label "Active connection to C2: $domain" `
-Detail "Local=$($c.LocalAddress):$($c.LocalPort) Remote=$($c.RemoteAddress):$($c.RemotePort) PID=$($c.OwningProcess)"
}
} else {
Write-OK "$domain — no active connection"
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# 5. SUSPICIOUS STRINGS in .json / .js / .mjs / .lock / .txt files
# (limited to user profile — not full disk scan)
# ═══════════════════════════════════════════════════════════════════════════════
Write-Header "5/5 — Suspicious strings in config / source files"
$stringScanExtensions = @('*.json', '*.js', '*.mjs', '*.lock', '*.txt', '*.yml', '*.yaml')
$stringScanRoots = @(
$env:USERPROFILE
$env:APPDATA
$env:LOCALAPPDATA
) | Where-Object { $_ -and (Test-Path $_) } | Sort-Object -Unique
# Build one combined regex for efficiency
$combinedPattern = ($SUSPICIOUS_STRINGS | ForEach-Object { [Regex]::Escape($_.Pattern) }) -join '|'
$combinedRegex = [System.Text.RegularExpressions.Regex]::new($combinedPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
# Track found strings to avoid duplicate reporting per string
$foundStrings = @{}
foreach ($root in $stringScanRoots) {
foreach ($ext in $stringScanExtensions) {
try {
$files = Get-ChildItem -Path $root -Recurse -Filter $ext `
-ErrorAction SilentlyContinue -Force 2>$null |
Where-Object { $_.Length -lt 5MB } # skip huge files
foreach ($f in $files) {
try {
$content = [System.IO.File]::ReadAllText($f.FullName, [System.Text.Encoding]::UTF8)
$m = $combinedRegex.Match($content)
if ($m.Success) {
$key = "$($f.FullName)|$($m.Value)"
if (-not $foundStrings.ContainsKey($key)) {
$foundStrings[$key] = $true
$ioc = $SUSPICIOUS_STRINGS | Where-Object {
$content -imatch [Regex]::Escape($_.Pattern)
} | Select-Object -First 1
Add-Finding -Severity $(if ($ioc) { $ioc.Severity } else { 'critical' }) `
-Category 'string' `
-Label "Campaign marker found: `"$($m.Value)`"" `
-Detail $f.FullName
}
}
} catch { <# skip unreadable files #> }
}
} catch { <# skip inaccessible dirs #> }
}
}
# Check compromised packages in package.json / package-lock.json
Write-Section "Compromised packages in package.json"
$pkgFiles = Get-ChildItem -Path $env:USERPROFILE -Recurse -ErrorAction SilentlyContinue `
-Include 'package.json','package-lock.json' -Force 2>$null |
Where-Object { $_.Length -lt 2MB -and $_.FullName -notmatch '\\node_modules\\' }
$pkgHits = @{}
foreach ($f in $pkgFiles) {
try {
$raw = [System.IO.File]::ReadAllText($f.FullName)
foreach ($pkg in $COMPROMISED_PACKAGES) {
if ($raw -imatch [Regex]::Escape($pkg)) {
$key = "$($f.FullName)|$pkg"
if (-not $pkgHits.ContainsKey($key)) {
$pkgHits[$key] = $true
Add-Finding -Severity 'critical' -Category 'package' `
-Label "Compromised package referenced: $pkg" `
-Detail $f.FullName
}
}
}
} catch { <# skip #> }
}
if ($pkgHits.Count -eq 0) {
Write-OK "No compromised packages found in package.json files"
}
# ═══════════════════════════════════════════════════════════════════════════════
# SUMMARY
# ═══════════════════════════════════════════════════════════════════════════════
Write-Header "SUMMARY — $hostname — $auditDate"
$criticalCount = @($findings | Where-Object { $_.Severity -eq 'critical' }).Count
$highCount = @($findings | Where-Object { $_.Severity -eq 'high' }).Count
$total = $findings.Count
if ($total -eq 0) {
Write-Host "`n ✔ NO ARTEFACTS FOUND — Likely false positive." -ForegroundColor Green
Write-Host " The previous report matched only command-history traces, not real files." -ForegroundColor Gray
Write-Host " Recommended: keep monitoring, no immediate escalation required.`n" -ForegroundColor Gray
} else {
Write-Host "`n ✘ $total finding(s): $criticalCount CRITICAL, $highCount HIGH" -ForegroundColor Red
Write-Host ""
if ($criticalCount -gt 0) {
Write-Host " MANDATORY INCIDENT RESPONSE ORDER:" -ForegroundColor Red
Write-Host " 1. ISOLATE the machine from the network NOW (do not revoke tokens yet)" -ForegroundColor Yellow
Write-Host " 2. PRESERVE evidence — do not reboot, do not delete files" -ForegroundColor Yellow
Write-Host " 3. IMAGE the disk if possible" -ForegroundColor Yellow
Write-Host " 4. KILL the gh-token-monitor daemon (if running)" -ForegroundColor Yellow
Write-Host " 5. THEN revoke and rotate all exposed secrets" -ForegroundColor Yellow
Write-Host " 6. ESCALATE to CSIRT with this output and the CSV report" -ForegroundColor Yellow
}
Write-Host "`n Findings detail:" -ForegroundColor DarkYellow
foreach ($f in $findings) {
$color = if ($f.Severity -eq 'critical') { 'Red' } elseif ($f.Severity -eq 'high') { 'Yellow' } else { 'Magenta' }
Write-Host " [$($f.Severity.ToUpper())][$($f.Category)] $($f.Label)" -ForegroundColor $color
if ($f.Detail) {
Write-Host " $($f.Detail)" -ForegroundColor DarkGray
}
}
Write-Host ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment