ForensicFoundry/ualforge

GitHub: ForensicFoundry/ualforge

将 Microsoft 365 统一审计日志 CSV 导出解析为 SQLite 数据库,并针对 BEC 调查优化字段索引的去重与查询工具。

Stars: 0 | Forks: 0

# ualforge 将 Microsoft 365 **统一审计日志 (UAL)** CSV 导出解析为单一的 SQLite 数据库,专为数字取证和**商业邮件妥协 (BEC)** 调查优化。 每个 CSV 导出的每一行都原样保留,JSON `auditData` blob 以原始形式(逐字节)和规范形式存储,并且大量 BEC 相关字段被提升为 **已索引且规范化**的列,以便快速查询。重新导入相同的数据是安全的:全行 SHA-256 哈希用作去重键。 配套脚本 `bec-triage` 使用此数据库生成 BEC 分诊报告。 请参阅 `bec-triage` 仓库[`此处`](https://github.com/ForensicFoundry/bec-triage) ## v2 中的新增功能 (schema_version = 2) 数据库 `user_version = 2`。请参阅[从 v1 迁移](#migrating-from-v1)。 - **全面的提升器** - 跨 Microsoft `RecordType` 架构名称不同的每个字段 (`ClientIP` vs `ClientIPAddress` vs `IPAddress`;`ApplicationId` vs `AppId`; 顶层 `UserAgent` vs `ExtendedProperties[Name='UserAgent'].Value` vs 嵌入在 `ActorInfoString` 内)现在通过别名链和每种 RecordType 专门的解析器来解决。这弥补了 `MailItemsAccessed`(RecordType 50) 事件的空白,在 v1 中这些事件会导致 `client_ip` / `user_agent` / `application_id` 为 NULL,尽管数据存在于规范 JSON 中。 - **摄入时规范化身份列:** - `client_ip` 去除端口(IPv4 `1.2.3.4:54321` -> `1.2.3.4`)并 去除 IPv6 括号;原始原值保存在新的 `client_ip_raw` 列中。 - `user_principal_name` 转换为小写;原始大小写保存在新的 `user_principal_name_raw` 列中。`bec-triage` v2 不再需要 `LOWER(user_principal_name) = LOWER(?)` 谓词。 - **两个新的结构化子表**,在摄入时填充: - `mail_items_accessed` - 来自 `auditData.Folders[].FolderItems[]` 的每个访问的邮件项目一行 (Microsoft 在每个审计行中聚合多次读取)。这让分析师能够用普通 `SELECT` 回答"actor 读取了哪些 InternetMessageIds?"。 - `consent_grants` - 每个(事件,目标应用)的 OAuth 同意 和服务主体事件一行。`app_id`、`app_display_name`、`consent_type`、 `is_admin_consent` 和 `permission_scope` 是一流索引列,而不是 埋在 `ModifiedProperties` JSON 中。 - **覆盖诊断。**每次摄入 / `--reextract` 运行都会计算并持久化 `client_ip` / `user_agent` / `application_id` / `user_principal_name` 填充百分比以及每条消息 / 同意授权计数到 `ingest_runs.coverage_json`。 `bec-triage` 在其报告横幅中显示这些信息,让分析师预先知道数据的完整程度。 - **`--reextract ` 子命令** 原地重新运行提升器针对数据库, 无需重新读取任何源 CSV。这是将 v1 数据库升级到 v2(或在未来的 提升器改进后刷新提升列)的方法——每个事件行都从 ualforge 始终保留的规范 JSON 重新派生。 ## 需求 - Python **3.13+**(仅使用 `from __future__ import annotations` 和现代标准库 特性——无第三方依赖) - SQLite 3.35+(任何现代 Linux/macOS/Windows 都附带此版本) ## 可选,但建议使用 - [`uv`](https://docs.astral.sh/uv/) `uv` 是我首选的 Python 包和项目管理器。我鼓励您也考虑使用它。 由于我使用 `uv`,安装说明将假设使用 `uv` 来创建和同步 Python venv。 ## 安装 ``` git clone https://github.com/ForensicFoundry/ualforge.git cd ualforge uv venv --python 3.13 ./.venv uv sync chmod +x ualforge sed -i "1c#!$(pwd)/.venv/bin/python" ualforge # 无需运行时依赖;无需安装其他任何东西 ``` ## 快速开始 ``` # 将 ./input/ 下任意位置的 UAL CSV 导入到 ./out/ualforge-YYYYMMDD.sqlite ./ualforge -o ./out ./input/ # 同上,但也会在数据库旁边写入 ualforge-YYYYMMDD-HHMMSS.log ./ualforge -o ./out -l ./input/ # 将更多 CSV 追加到现有的 ualforge 数据库中 ./ualforge -a ./out/ualforge-20260427.sqlite ./more-input/ ``` 如果您在同一天重新运行相同的输入,行会被去重,并且对于已存在的任何行, 磁盘上的数据库保持不变。 ## 用法参考 ``` usage: ualforge [-h] [-v] (-o DIR | -a DB | --reextract DB) [-l] [INPUT_DIR] positional arguments: INPUT_DIR directory to recursively scan for UAL CSV files (required for -o / -a; ignored for --reextract) options: -h, --help show this help message and exit -v, --version print version and exit target (mutually exclusive, exactly one required): -o, --output DIR write to /ualforge-YYYYMMDD.sqlite (creates DIR if needed; same-day re-runs append to the same file) -a, --append DB append to a pre-existing ualforge database at PATH (must be a valid ualforge database, verified via PRAGMA application_id + ualforge_meta table + user_version) --reextract DB rebuild promoted columns + child tables in place from the preserved auditData JSON, without re-reading any source CSV. Used to upgrade a v1 database to v2, or to refresh promoted values after a future promoter improvement. logging: -l, --log write a ualforge-YYYYMMDD-HHMMSS.log next to the database capturing all stdout output for the run ``` 退出代码: - `0` 成功(即使某些行有 JSON 解析错误;这些会记录在 `parse_errors` 中) - `1` 运行时错误(无法读取输入、无法写入数据库等) - `2` 无效的 CLI 用法 / 追加目标验证失败 ## 输出:数据库文件 `./out/ualforge-YYYYMMDD.sqlite` 是启用了 WAL 模式的标准 SQLite 数据库。 使用以下方式打开: - `sqlite3` CLI - DB Browser for SQLite (`sqlitebrowser`) - 任何支持 SQLite 的客户端(DBeaver、JetBrains DataGrip、DuckDB 等) - 配套的 **`bec-triage`** 脚本 - 请参阅 `bec-triage` 仓库[`此处`](https://github.com/ForensicFoundry/bec-triage) 同一天的重新运行追加到同一文件;文件名中的日期是**首次**摄入发生的日期。 使用 `-a / --append` 将数据添加到在不同日期创建的数据库。 数据库携带 SQLite `application_id` 为 `0x55414L48`(ASCII `UALH`) 和 `user_version` 为 `2`(自 v2026.05.01 起;v1 数据库可以通过 `--reextract` 原地升级)。追加模式和 `bec-triage` 会检查这些值。 ## 配套脚本:bec-triage `bec-triage` 使用 `ualforge` 生成的数据库来生成彩色 BEC 分诊报告,包括: 注意事项 / 方法论、工具指纹、行为异常筛选(UA 独立启发式)、 候选被入侵的 UPN、OAuth 应用滥用、源 IP 分析、身份验证支点、 泄露时间线、持久性(收件箱规则、传输规则和邮箱权限)指标、 OAuth 同意授权时间线、每条消息的泄露深度分析,以及可选的 `--per-upn` 聚焦报告。 `bec-triage` v2 期望 v2 数据库(`PRAGMA user_version == 2`)并且 将拒绝在 v1 数据库上运行,并显示指向 `--reextract` 的明确错误。 请参阅 `bec-triage` 自己的 README 了解用法和方法论。 ## 配套脚本:ual-normalize `ualforge` 强制执行严格的 13 列标题(规范的 Microsoft 365 / Purview Audit 门户导出架构)。请参阅 `ual-normalize` 仓库[`此处`](https://github.com/ForensicFoundry/ual-normalize) 野外的 UAL 证据通常以**其他**形式出现: | 来源格式 | 典型来源 | 外观 | |---|---|---| | **规范**(13 列)| Purview 门户 CSV 下载 / Microsoft Graph 审计导出转换器 | `id, createdDateTime, ..., auditData` | | **PowerShell**(约 13 列)| `Search-UnifiedAuditLog` cmdlet 输出通过管道传送到 `Export-Csv` | `RunspaceId, PSComputerName, CreationDate, UserIds, Operations, RecordType, AuditData, ResultIndex, ResultCount, Identity, IsValid, ObjectState` | | **Splunk 重新导出**(40+ 列)| Splunk 对索引化的 PowerShell UAL 源的 `outputcsv` | PowerShell 列 + `_bkt, _cd, _raw, _si, _sourcetype, _time, splunk_server, ...` | | **仅 AuditData** | 自定义工具、第三方 SIEM 转储 | 单个 `AuditData` 列(或该列加上任意不相关列)| 所有这些都在 `AuditData` 列中嵌入相同的每事件 JSON,而该 JSON 正是 `ualforge` 的提升列和 `bec-triage` 的分析实际依赖的。 `ual-normalize` 读取四种形式中的任何一种,将每行重新投影到规范 13 列 (当周围列缺失或命名不同时,从 JSON 派生 `id`、`createdDateTime`、 `auditLogRecordType`、`operation`、`organizationId`、`userType`、`userId`、`service`、 `objectId`、`userPrincipalName` 和 `clientIp`),并写入 `ualforge` 可直接接受的干净 CSV。 ## 示例查询 以下是 `bec-triage` 执行的一些示例查询。 ``` -- All FileDownloaded events for a UPN, sorted by time SELECT created_at_utc, client_ip, user_agent, object_id FROM events WHERE user_principal_name = 'user@example.com' AND operation = 'FileDownloaded' ORDER BY created_at_utc; -- Top user agents by event count SELECT user_agent, COUNT(*) AS hits FROM events WHERE user_agent IS NOT NULL GROUP BY user_agent ORDER BY hits DESC LIMIT 20; -- Inbox rule changes per UPN per day SELECT user_principal_name, substr(created_at_utc, 1, 10) AS day, operation, COUNT(*) AS hits FROM events WHERE operation IN ('New-InboxRule','Set-InboxRule','Remove-InboxRule') GROUP BY user_principal_name, day, operation ORDER BY day, hits DESC; -- Drill into the raw rule contents SELECT created_at_utc, user_principal_name, operation, json_extract(audit_data_canonical, '$.Parameters') FROM events WHERE operation IN ('New-InboxRule','Set-InboxRule') ORDER BY created_at_utc DESC LIMIT 50; -- Verify provenance for a specific row SELECT source_file, source_line, export_batch, ingested_at, parse_error FROM events WHERE id = '...the GUID from the original CSV...'; -- Audit the ingest run history SELECT run_id, started_at, ended_at, files_processed, rows_inserted, rows_duplicate, rows_with_errors, status FROM ingest_runs ORDER BY run_id; ``` ## 更多信息 如需更多、更深入的信息,请阅读 `MANUAL.md`。 ## 许可证 GPL-3.0-or-later
标签:Azure AD, BEC, CSV解析, DAST, Exchange Online, IP规范化, JSON解析, Microsoft 365, Office 365, SHA-256, SQLite, UAL, 云取证, 取证调查, 商业邮件妥协, 域环境安全, 子域名变形, 审计日志, 恶意软件分析, 数字取证, 数据去重, 数据库导入, 用户身份规范化, 统一审计日志, 网络安全, 网络安全审计, 自动化脚本, 逆向工具, 邮件安全, 钓鱼调查, 隐私保护