简要说明: 如果你的环境中出现了 postmark-mcp@1.0.16,请将其视为已受损,并立即进行补救措施。 🚨
<br/>
最近披露的一个恶意npm包 postmark-mcp(一个用于Postmark的MCP连接器)在发布版本 1.0.16 中包含了一个后门,该后门会悄悄地将发出的邮件BCC到攻击者控制的地址。该包被发现后从注册表中删除了,但已经安装了该版本的系统仍然可能在泄露数据。这是一个典型的供应链攻击,影响了连接工具——因为这些库处理电子邮件(令牌、密码重置、发票),所以影响很大。 😬
postmark-mcp (npm)1.0.16 及之后的版本(恶意发布)phan@giftshop[.]clubgiftshop[.]club如果你看到流量指向
giftshop.club或邮件转发至phan@giftshop.club,请视为高度可疑。 🔍
MCP连接器是AI/邮件工具使用的信任组件。它们:
以下是两个实用的扫描器(Python 和 PowerShell),用于在代码库或环境中查找 postmark-mcp 的实例。从工作区根目录(例如 ~/Projects)运行它们,以识别直接安装、锁文件引用或文字提及。
如果检测到版本
1.0.16:立即遵循以下补救检查清单。
scan_postmark_mcp.py)递归扫描:
package-lock.json / npm-shrinkwrap.json 依赖树package.json 条目node_modules/postmark-mcp/package.json(已安装副本).json, .js, .ts, .lock, .md, .txt 文件中的文本匹配#!/usr/bin/env python3
"""
scan_postmark_mcp.py
用法:
python3 scan_postmark_mcp.py [开始路径]
递归扫描“postmark-mcp”的证据,并标记版本1.0.16为受损。
"""
import sys, os, json, re
开始 = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
目标 = "postmark-mcp"
受损版本 = "1.0.16"
发现 = []
def 检查_package_json_文件(路径):
try:
with open(路径, "r", encoding="utf-8", errors="ignore") as f:
数据 = json.load(f)
except Exception:
return
for 关键词 in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
依赖项 = 数据.get(关键词) or {}
if isinstance(依赖项, dict) and 目标 in 依赖项:
版本 = 依赖项.get(目标, "未知")
发现.append((路径, "package.json-deps", 版本))
def 遍历_package_lock(路径):
try:
with open(路径, "r", encoding="utf-8", errors="ignore") as f:
数据 = json.load(f)
except Exception:
return
def 遍历_依赖项(依赖项, 路径):
if not isinstance(依赖项, dict):
return
for 名称, 信息 in 依赖项.items():
if 名称 == 目标:
版本 = 信息.get("version") or 信息.get("resolved") or "未知"
发现.append((路径 + " -> " + " > ".join(路径 + [名称]), "package-lock", 版本))
if isinstance(信息, dict):
嵌套 = 信息.get("dependencies") or 信息.get("requires")
if 嵌套:
遍历_依赖项(嵌套, 路径 + [名称])
依赖项 = 数据.get("dependencies") or {}
遍历_依赖项(依赖项, [])
def 检查_node_module_目录(起始目录):
nm_路径 = os.path.join(起始目录, "node_modules", 目标, "package.json")
if os.path.exists(nm_路径):
try:
with open(nm_路径, "r", encoding="utf-8", errors="ignore") as f:
数据 = json.load(f)
版本 = 数据.get("version", "未知")
except Exception:
版本 = "未知"
发现.append((nm_路径, "node_modules", 版本))
def 原始文本检查(路径):
try:
with open(路径, "r", encoding="utf-8", errors="ignore") as f:
文本 = f.read()
except Exception:
return
if 目标 in 文本:
匹配 = re.search(r"postmark-mcp[\"']?\s*[:@]?\s*([0-9]+\.[0-9]+\.[0-9]+)", 文本)
版本 = 匹配.group(1) if 匹配 else "未知"
发现.append((路径, "原始文本", 版本))
for 根, 目录, 文件 in os.walk(开始):
文件集 = set(文件)
if "package-lock.json" in 文件集:
遍历_package_lock(os.path.join(根, "package-lock.json"))
if "npm-shrinkwrap.json" in 文件集:
遍历_package_lock(os.path.join(根, "npm-shrinkwrap.json"))
if "package.json" in 文件集:
检查_package_json_文件(os.path.join(根, "package.json"))
检查_node_module_目录(根)
for 文件名 in 文件:
if 文件名.endswith((".json",".js",".ts",".lock",".txt",".md")):
文件路径 = os.path.join(根, 文件名)
try:
if os.path.getsize(文件路径) <= 1024*1024:
原始文本检查(文件路径)
except Exception:
pass
if not 发现:
print("✅ 在以下位置未发现postmark-mcp的证据:", 开始)
else:
print("⚠️ 在以下位置发现postmark-mcp的证据:", 开始)
for 路径, 类型, 版本 in 发现:
是否受损 = (版本 == 损失版本)
标志 = "🚨 已受损 (1.0.16)" if 是否受损 else ""
print(f" - [{类型}] {路径} — 版本: {版本} {标志}")
if 发现:
print("\n建议下一步操作:")
print(" 1) 对于每个项目,运行 npm uninstall postmark-mcp")
print(" 2) 对于1.0.16:移除 node_modules + package-lock.json 并运行 npm install 新鲜安装")
print(" 3) 旋转相关密钥并审计邮件日志")
如何运行:
python3 scan_postmark_mcp.py /路径/到/开始 # 默认为当前目录
或者
python scan_postmark_mcp.py /路径/到/开始 # 默认为当前目录
<img width="1120" height="188" alt="Python截图" src="https://gips2.baidu.com/it/u=2361160037,3043267302&fm=3081&app=3081&f=PNG?w=1120&h=188" /> <br/>
scan_postmark_mcp.ps1<#
.SYNOPSIS
扫描postmark-mcp的出现情况,并可选地卸载它们。
.DESCRIPTION
- 递归搜索package*.json和锁文件中的"postmark-mcp"出现情况。
- 查找node_modules下的已安装副本(*/node_modules/postmark-mcp)。
- 报告路径和发现的版本(如果有package.json)。
- 如果使用-AutoUninstall参数,则尝试在项目目录中运行`npm uninstall postmark-mcp`,并在全局安装时运行`npm uninstall -g postmark-mcp`。
- 如果不使用-AutoUninstall参数,则在卸载每个命中点之前提示确认。
.PARAMETER 路径
要扫描的根路径。默认为当前目录。
.PARAMETER AutoUninstall
如果存在,则自动执行卸载操作(无需逐项提示)。
.EXAMPLE
.\scan_postmark_mcp.ps1 -Path C:\Projects
.\scan_postmark_mcp.ps1 -AutoUninstall
#>
param(
[string]$路径 = ".",
[switch]$AutoUninstall
)
函数 确保Npm {
try {
$npm = & npm --version 2>$null
if ($LASTEXITCODE -ne 0) { throw "npm未找到或不在PATH中。" }
return $true
} catch {
Write-Warning "npm不在PATH中。请安装Node.js/npm或在具有npm的机器上运行此命令。"
return $false
}
}
函数 报告并卸载($项目目录, $发现路径, $版本) {
$相对 = (Resolve-Path $项目目录).Path
Write-Host "发现:$发现路径 (项目根目录:$相对) 版本:$版本" -ForegroundColor Yellow
if (-not (确保Npm)) { return }
$执行 = $AutoUninstall.IsPresent
if (-not $执行) {
$答案 = Read-Host "是否从$相对卸载postmark-mcp?(y/N)"
$执行 = $答案 -match '^(y|yes)$'
}
if ($执行) {
Write-Host " -> 正在运行:npm uninstall postmark-mcp (在$相对)"
try {
Push-Location $相对
& npm uninstall postmark-mcp 2>&1 | Write-Host
Pop-Location
# 尝试删除剩余的node_modules/postmark-mcp文件夹
$nm路径 = Join-Path $相对 "node_modules\postmark-mcp"
if (Test-Path $nm路径) {
Write-Host " -> 删除遗留文件夹:$nm路径"
Remove-Item -Recurse -Force $nm路径 -ErrorAction SilentlyContinue
}
Write-Host " -> 卸载尝试完成:$相对" -ForegroundColor Green
} catch {
Write-Warning "卸载失败:$相对。错误:$_"
if (Test-Path $相对) { Pop-Location 2>$null }
}
} else {
Write-Host " -> 跳过卸载:$相对(用户选择不卸载)" -ForegroundColor Cyan
}
}
Write-Host "正在扫描postmark-mcp:$路径" -ForegroundColor Cyan
# 1) 搜索package*.json和锁文件中的文字提及
$package文件 = Get-ChildItem -Path $路径 -Recurse -Include package.json, package*.json, package-lock.json, npm-shrinkwrap.json -File -ErrorAction SilentlyContinue
$命中 = @()
foreach ($f in $package文件) {
try {
$内容 = Get-Content -Raw -Encoding UTF8 $f.FullName -ErrorAction SilentlyContinue
if ($内容 -match "postmark-mcp") {
# 尝试提取JSON中的版本
$版本 = "未知"
try {
$json = $null
$json = $内容 | ConvertFrom-Json -ErrorAction Stop
if ($json -ne $null) {
foreach ($k in "dependencies","devDependencies","peerDependencies","optionalDependencies") {
if ($json.PSObject.Properties.Name -contains $k) {
$依赖项 = $json.$k
if ($依赖项 -and $依赖项["postmark-mcp"]) { $版本 = $依赖项["postmark-mcp"] }
}
}
}
} catch { $版本 = "未知" }
$命中 += [PSCustomObject]@{ 路径 = $f.FullName; 类型 = "清单/锁文件"; 版本 = $版本 }
}
} catch { }
}
# 2) 检查node_modules文件夹中的已安装副本
$nm目录 = Get-ChildItem -Path $路径 -Recurse -Directory -Force -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like "*\node_modules\postmark-mcp" -or $_.FullName -like "*/node_modules/postmark-mcp" }
foreach ($d in $nm目录) {
$pkg = Join-Path $d.FullName "package.json"
$版本 = "未知"
if (Test-Path $pkg) {
try {
$pj = Get-Content $pkg -Raw | ConvertFrom-Json
$版本 = $pj.version
} catch { $版本 = "未知" }
}
$项目根目录 = ($d.FullName -replace "(.*)(\\|/)node_modules\\postmark-mcp$",'$1')
$命中 += [PSCustomObject]@{ 路径 = $d.FullName; 类型 = "node_modules"; 版本 = $版本; 项目根目录 = $项目根目录 }
}
# 3) 打印发现结果
if ($命中.Count -eq 0) {
Write-Host "未在以下位置发现postmark-mcp的出现情况:$路径" -ForegroundColor Green
} else {
Write-Host "发现的出现情况:" -ForegroundColor Yellow
foreach ($h in $命中) {
if ($h.类型 -eq "node_modules") {
Write-Host " - [node_modules] $($h.路径) 版本:$($h.版本)"
} else {
Write-Host " - [$($h.类型)] $($h.路径) 版本:$($h.版本)"
}
}
# 提供每个项目的卸载选项
# 收集唯一的项目根目录以在其中运行npm卸载
$项目根目录 = @()
foreach ($h in $命中) {
if ($h.项目根目录) { $项目根目录 += $h.项目根目录 }
else { $项目根目录 += (Split-Path $h.路径 -Parent) }
}
$项目根目录 = $项目根目录 | Sort-Object -Unique
foreach ($proj in $项目根目录) {
$版本 = ($命中 | Where-Object { ($_.项目根目录 -eq $proj) -or (Split-Path $_.路径 -Parent -eq $proj) } | Select-Object -First 1).版本
报告并卸载 -项目目录 $proj -发现路径 $proj -版本 $版本
}
# 检查并卸载全局副本(如果存在)
if (确保Npm) {
try {
$全局检查 = & npm ls -g postmark-mcp --depth=0 2>$null
if ($全局检查 -and $全局检查 -match "postmark-mcp@") {
Write-Host "`n全局postmark-mcp似乎已安装。尝试全局卸载?" -ForegroundColor Yellow
$执行全局 = $AutoUninstall.IsPresent
if (-not $执行全局) {
$答案 = Read-Host "是否卸载全局postmark-mcp?(y/N)"
$执行全局 = $答案 -match '^(y|yes)$'
}
if ($执行全局) {
Write-Host " -> 正在运行:npm uninstall -g postmark-mcp"
& npm uninstall -g postmark-mcp 2>&1 | Write-Host
Write-Host " -> 全局卸载尝试完成。" -ForegroundColor Green
} else {
Write-Host " -> 跳过全局卸载。" -ForegroundColor Cyan
}
} else {
Write-Host "未检测到全局安装的postmark-mcp。"
}
} catch { Write-Warning "无法检查全局npm包。" }
}
}
Write-Host "`n扫描完成。" -ForegroundColor Cyan
扫描当前文件夹: