PSScript/Incident-Response
GitHub: PSScript/Incident-Response
一个用于应对 Exchange Online 邮件/订阅轰炸事件的 PowerShell 模块,通过时间窗口定位恶意邮件并执行可恢复、可追溯的批量清理。
Stars: 0 | Forks: 0
# IncidentResponse
用于处理 Exchange Online 中**邮件轰炸或订阅轰炸事件**的 PowerShell 模块。
这些命令会查找受影响的消息,记录它们的位置,允许进行受控选择,并以可追溯的方式将其删除。在保留期内,所有的删除操作都是可恢复的。
## 目录
1. [什么是邮件轰炸](#was-ist-mail-bombing)
2. [安装说明](#installation)
3. [应用注册与权限](#app-registrierung-und-berechtigungen)
4. [配置(数据作为配置)](#konfiguration-daten-als-konfiguration)
5. [工作文件夹](#arbeitsordner)
6. [处理流程](#ablauf)
7. [命令](#befehle)
8. [恢复说明](#wiederherstellung)
9. [故障排除](#fehlerbehebung)
10. [安全注意事项](#sicherheitshinweise)
## 什么是邮件轰炸
在订阅轰炸中,攻击者会将受害者的电子邮件地址输入到成千上万个新闻通讯和注册表单中。在几分钟内,就会收到数以万计的**合法**确认邮件。
这对于应对攻击至关重要:
* 发件人是真实的,且拥有良好的信誉。信誉过滤器不起作用。
* 发件人域名几乎每封邮件都是唯一的。黑名单毫无用处。
* 目的通常是为了掩盖某条重要的单条消息(订单确认、密码重置、欺诈警告)。
因此,该模块**不是**基于发件人进行处理的,而是通过攻击的**时间窗口**加上人工的视觉检查。
## 安装说明
不需要 `Microsoft.Graph` 模块——这些命令直接调用 Graph-API。
```
# 克隆 Repository 或解压 ZIP,然后:
Import-Module .\IncidentResponse.psd1
Get-Command -Module IncidentResponse
```
如需生成 Excel 报告,另需安装:
```
Install-Module ImportExcel -Scope CurrentUser
```
### 加载模块的三种方式
| 方式 | 命令 | 适用场景 |
|---|---|---|
| 模块 | `Import-Module .\IncidentResponse.psd1` | 常规情况 |
| Dot-Sourcing | `. .\IncidentResponse.psm1` | 快速,无需清单 |
| 粘贴 | 将整个 `.psm1` 粘贴到控制台中 | 在没有文件访问权限的第三方计算机上 |
该模块会自动检测它是作为模块加载还是被完全粘贴到控制台中,并在上述三种情况下都提供相应的命令。
## 应用注册与权限
### 注册
1. **Microsoft Entra ID > 应用注册 > 新注册**,名称例如 `IR-MailBombing`
2. 记录**应用程序(客户端)ID** 和**目录(租户)ID**
### 密钥或证书
**密钥(更快):** 证书与密钥 > 新客户端密钥 > 复制值。
**证书(推荐):**
```
$cert = New-SelfSignedCertificate -Subject "CN=IR-MailBombing" `
-CertStoreLocation "Cert:\CurrentUser\My" -KeySpec Signature `
-NotAfter (Get-Date).AddMonths(6)
Export-Certificate -Cert $cert -FilePath "$env:TEMP\IR-MailBombing.cer"
$cert.Thumbprint
```
在“证书”下上传 `.cer` 文件。
### 权限
**API 权限 > Microsoft Graph > 应用程序权限**
| 权限 | 用途 | 必需 |
|---|---|---|
| `Mail.ReadWrite` | 读取邮箱,删除消息 | 是 |
| `ThreatHunting.Read.All` | Defender Advanced Hunting | 仅用于 `Get-MailBombDefenderPreflight` |
之后**务必授予管理员同意**。如果没有此步骤:将会出现 HTTP 403。
### 限制访问(强烈推荐)
作为应用程序权限,`Mail.ReadWrite` 允许访问**所有**邮箱。请在事件结束后对其进行限制或删除注册:
```
New-ApplicationAccessPolicy -AppId `
-PolicyScopeGroupId ir-scope@ihre-domain.de `
-AccessRight RestrictAccess -Description "Incident Response"
Test-ApplicationAccessPolicy -Identity nutzer@ihre-domain.de -AppId
```
## 配置(数据作为配置)
所有特定于客户/租户的值都位于 `config.json` 中,而不是代码中。这使得该模块无需修改即可重复使用。
```
{
"defaultTenant": "CST",
"tenants": {
"CST": {
"tenantId": "00000000-0000-0000-0000-000000000000",
"clientId": "00000000-0000-0000-0000-000000000000",
"clientSecret": "",
"certificateThumbprint": "",
"recipientSuffix": "ihre-domain.de",
"internalDomains": ["ihre-domain.de"],
"keepSenders": [],
"hardKeepMatch": [],
"legitTlds": ["de", "com"]
}
}
}
```
**多个租户:** 在 `tenants` 下创建其他部分。**租户缩写**(此处为 `CST`)用于选择该部分,并同时定义文件夹名称:
```
\Incident-Response\CST\
```
这样,不同客户的运行数据就永远不会混在同一个数据集中。如果不加 `-Tenant` 参数,则使用 `defaultTenant`。
| 字段 | 含义 |
|---|---|
| `recipientSuffix` | 自有的邮件域,用于限制 Defender 查询范围 |
| `internalDomains` | 自有的发件人域——永远不会被删除 |
| `keepSenders` | 始终保留的单独发件人地址 |
| `hardKeepMatch` | 与 TLD 无关而需保留的子字符串(供应商、制造商、专业应用)。具有行业特异性——在此处填入,不要写在代码里。 |
| `legitTlds` | 匹配到这些 TLD 的结果将进入人工视觉检查,而不是被自动删除 |
`hardKeepMatch` 特意默认留空。在首次运行时,`domains-to-kill.csv` 会显示受影响的域——从中提取您自己的业务伙伴并添加进去。
填写 `clientSecret` **或** `certificateThumbprint` 之一即可。参数(如 `-TenantId` 等)的优先级高于配置文件。
**设置文件权限**——该文件包含具有对所有邮箱完全访问权限的密钥:
```
icacls .\config.json /inheritance:r /grant:r "$env:USERNAME:(R)" "SYSTEM:(F)" "Administratoren:(F)"
```
## 工作文件夹
所有命令都会将结果存放在一个共同的工作文件夹中:
```
\Incident-Response\\
```
自动创建。允许路径中存在空格(如 `C:\Users\Max Mustermann\...`),因为内部全程使用了 `[IO.Path]::Combine` 和 `-LiteralPath` 进行处理。
**在多个步骤中使用相同的文件夹**——只需设定一次,然后在各处传入。当包含空格时,引号是必需的:
```
$IR = "$env:USERPROFILE\Desktop\Incident-Response\CST\$(Get-Date -Format 'yyyyMMdd-HHmm')"
New-Item -ItemType Directory -Path "$IR" -Force | Out-Null
# 将 config.json 复制到此处,然后在每个命令中加上 -WorkFolder "$IR"
```
自动生成的子文件夹:`kill\`、`defender\`、`report\`、`recon\`。
## 处理流程
```
1. Get-MailBombDefenderPreflight (optional) Zeitfenster + betroffene Nutzer
2. Get-MailBombLocation Postfaecher durchsuchen
3. New-MailBombKillList Auswahl (Sichtpruefung)
4. Invoke-MailBombPurge -WhatIf Probelauf
5. Invoke-MailBombPurge Loeschung
6. New-MailBombStatusReport Statusbericht
7. New-MailBombWorkbook (optional) Excel-Mappe
```
### 完整流程
```
Import-Module .\IncidentResponse.psd1
$IR = "$env:USERPROFILE\Desktop\Incident-Response\CST\$(Get-Date -Format 'yyyyMMdd-HHmm')"
New-Item -ItemType Directory -Path "$IR" -Force | Out-Null
# 将 config.json 复制到 "$IR"
# 1. 可选:从 Defender 中提取 Angriffsfenster
Get-MailBombDefenderPreflight -WorkFolder "$IR" -Tenant CST `
-FromLocal '2026-07-23 13:00' -ToLocal '2026-07-23 18:00'
# 2. 搜索邮箱(只读)
Get-MailBombLocation -WorkFolder "$IR" -Tenant CST `
-InputPath "$IR\betroffene.csv" `
-FromLocal '2026-07-23 14:30' -ToLocal '2026-07-23 15:30' `
-ReportPath "$IR\location.csv" -Verbose
# 3. 进行选择
New-MailBombKillList -WorkFolder "$IR" -Tenant CST `
-ReportPath "$IR\location.csv" -FromScan
# 4. 试运行
Invoke-MailBombPurge -WorkFolder "$IR" -Tenant CST `
-ReportPath "$IR\kill\report-fixed.csv" `
-InputPath "$IR\kill\filtered-to-be-killed.csv" `
-LogPath "$IR\whatif.csv" -WhatIf -Verbose
# 5. 删除
Invoke-MailBombPurge -WorkFolder "$IR" -Tenant CST `
-ReportPath "$IR\kill\report-fixed.csv" `
-InputPath "$IR\kill\filtered-to-be-killed.csv" `
-DeleteMode HardDelete -LogPath "$IR\run.csv" -Confirm:$false -Verbose
# 6. 报告
New-MailBombStatusReport -WorkFolder "$IR" `
-ScanPath "$IR\location.csv" `
-KillPath "$IR\kill\filtered-to-be-killed.csv" `
-RunLogs "$IR\run.csv"
# 7. Excel-Mappe
New-MailBombWorkbook -WorkFolder "$IR"
```
### 输入文件
第 2 步需要一个邮箱列表。至少需包含 `owner` 和 `message_id` 列,以分号分隔:
```
owner;message_id
nutzer1@ihre-domain.de;
```
`message_id` = **Internet Message-ID** (RFC 5322),而不是 Message-Trace-ID。尖括号可选。
## 命令
| 命令 | 类型 | 目的 |
|---|---|---|
| `Get-MailBombDefenderPreflight` | 只读 | 从 Defender 获取受影响的用户 + 攻击时间线(15分钟为一个时间段) |
| `Get-MailBombLocation` | 只读 | 搜索邮箱,记录发现位置 |
| `New-MailBombKillList` | 只读 | 进行选择(两个 `Out-GridView` 窗口) |
| `Invoke-MailBombPurge` | **写入** | 执行包含四项检查 + `-WhatIf` 的删除操作 |
| `Get-MailBombReconciliation` | 只读 | 对比核对数据源 |
| `New-MailBombStatusReport` | 离线 | 生成各邮箱的状态报告 |
| `New-MailBombWorkbook` | 离线 | 生成 Excel 工作簿 (`ImportExcel`) |
| `Remove-MailBombMessages` | 写入 | 适用于小型事件的独立版本 |
`Get-Help <命令> -Full` 可提供每个命令的详细说明和示例。
### 在 New-MailBombKillList 中进行选择
规则顺序,匹配到第一个即生效:内部域 > `mailer-daemon`/`postmaster` > `-KeepSenders` > 来自用户文件夹的发件人 > `-HardKeepMatch`(来自 config.json 的自有供应商) > 具有业务关联的 `.de` 域 > 视觉检查。
* **窗口 1**(`.com` 等,可能是账单):标记需要**保留**的内容。
* **窗口 2**(无业务关联的 `.de`):标记需要**删除**的内容。
### 删除模式 (Invoke-MailBombPurge)
| 模式 | 效果 | 用户可见 |
|---|---|---|
| `SoftDelete` | 已删除项目 | 是,可恢复 |
| `Recoverable` | `RecoverableItems\Deletions` | 通过“恢复已删除项目”可见 |
| `HardDelete` | `RecoverableItems\Purges` | 否,但管理员可恢复 |
**中断后继续:** 复制日志,然后使用 `-ResumeFromLog`——逐一跳过已删除的项目。`-ExcludeMailbox` 可跳过整个邮箱。
## 恢复说明
在保留期内,所有的删除操作都是可逆的。
**删除前检查:**
```
Get-Mailbox nutzer@ihre-domain.de |
Format-List RetainDeletedItemsFor, SingleItemRecoveryEnabled, LitigationHoldEnabled
```
预期结果:`SingleItemRecoveryEnabled = True`,`RetainDeletedItemsFor = 14.00:00:00`。如果未启用:
```
Set-Mailbox nutzer@ihre-domain.de -SingleItemRecoveryEnabled $true -RetainDeletedItemsFor 30
```
**删除后:**
```
Get-MailboxFolderStatistics nutzer@ihre-domain.de -FolderScope RecoverableItems |
Format-Table Name, ItemsInFolder
```
`Purges` 的数量应该增加了与已删除数量相当的值。
**恢复:**
```
Get-RecoverableItems -Identity nutzer@ihre-domain.de `
-SubjectContains "Betreff" -FilterItemType IPM.Note | Restore-RecoverableItems
```
日志 (`run.csv`) 中记录了所有的 Message-ID。
## 故障排除
| 消息 | 原因 | 解决方案 |
|---|---|---|
| 启动时出现 `HTTP 403` | 缺少管理员同意 | 在门户中授予同意 |
| `Mail.ReadWrite is not in the token roles` | 是委托权限而非应用程序权限 | 添加为**应用程序权限** |
| `Preflight read ... failed` | 被 Application Access Policy 阻止 | 运行 `Test-ApplicationAccessPolicy` |
| 出现大量 `429` | 频率限制 | 使用 `-Lanes 1` |
| 删除时出现大量 `404` | 项目已被移动 | 非关键错误,可忽略 |
| `Token request failed` | 密钥已过期 | 生成新密钥 |
| 缺少 `Out-GridView` | 无 GUI 的 PS7 | 使用 PS 5.1 或加 `-NoGrid` |
| Excel 崩溃 | 缺少 `ImportExcel` | 运行 `Install-Module ImportExcel` |
Exchange Online 允许每个应用和邮箱大约同时发送 4 个并发请求。默认为 `-Lanes 2`,如果持续出现 429 错误,则降为 `1`。
## 安全注意事项
* **`config.json` 包含高权限密钥**(可访问所有邮箱)。请勿提交到代码库,需限制访问权限,并在事件结束后进行轮换。`.gitignore` 已将其屏蔽。
* **事件结束后移除应用注册**,或通过 Application Access Policy 进行限制。
* **始终先使用 `-WhatIf`。** 试运行会在不进行任何更改的情况下生成完整的日志。
* **先处理单个邮箱**(`-OnlyMailbox`)。
* 在执行 `HardDelete` 前**检查保留期**。
* **保留日志**——作为何时删除了什么内容的证明。
### 事件结束后
邮件轰炸通常是为了掩盖某些事情。需额外检查:
* 受影响账户在攻击时间段内的登录日志
* 新的收件箱规则 (`Get-InboxRule`)
* 时间窗口内的交易邮件(订单、密码重置)
* 未送达报告 -> **代表**您的域名发送 -> 检查 DMARC/SPF
## 许可证
MIT
标签:AI合规, Exchange Online, IPv6, Libemu, PowerShell, 自动化运维, 邮件安全, 配置错误