oscarmorberg-ops/s3-nist-scanner

GitHub: oscarmorberg-ops/s3-nist-scanner

一款轻量级 Rust AWS 只读安全扫描器,专注检测 S3 与 IAM 访问控制风险并提供可解释的 CIS 对齐发现。

Stars: 0 | Forks: 0

# s3-nist-scanner 一个轻量级的 Rust CLI 工具,用于检查 S3、IAM 和 GuardDuty 中的 AWS 访问控制信号 — 为 AWS 账户中可能存在的最小权限违规提供快速、可解释的初步视图。 它的范围特意做得很窄:作为访问控制审查的实用起点,而不是 CSPM 平台或合规性证明工具。 ## 检查内容 **高严重性** - **S3 Block Public Access** — 账户级别和存储桶级别,涵盖全部四个标志。 - **S3 存储桶策略** — 包含通配符主体(`principal:*`)的 `Allow` 语句。 - **S3 ACL** — 授予 `AllUsers` 或 `AuthenticatedUsers` 访问权限的存储桶。 - **IAM 角色和用户策略** — 通配符 S3 操作(`s3:*`、`s3:Get*` 等)或在 `*` 上的 `iam:PassRole`,适用于内联和附加策略。 - **Root 账户访问密钥** — Root 账户上的活跃访问密钥;绕过所有 IAM 控制且无法进行范围限制。 - **Root 账户 MFA** — 没有配置 MFA 设备的 Root 账户;仅靠密码保护完整的账户访问权限。 - **GuardDuty 发现** — 所有 17 个标准商业区域中的活跃(非存档)发现。 **中严重性** - **S3 访问日志** — 未启用服务器访问日志的存储桶。 - **S3 强制 HTTPS** — 未对 `aws:SecureTransport: false` 设置 `Deny` 的存储桶。 - **S3 版本控制** — 禁用了版本控制的存储桶(无法防范删除或勒索软件覆盖)。 - **S3 对象所有权** — 仍启用了 ACL 的存储桶(未设置 `BucketOwnerEnforced`)。 - **S3 Object Lock** — 未开启 Object Lock 的版本控制存储桶(任何具有写入权限的人都可以永久删除版本)。 - **IAM 访问密钥期限** — 超过 90 天的活跃访问密钥或在创建后从未使用过的访问密钥 (CIS AWS 1.14)。 ## 如何运行 **前置条件:** Rust 工具链,已配置的 AWS 凭证(默认 profile 或 `AWS_PROFILE`)。扫描器需要对 S3、IAM、GuardDuty、CloudTrail、IAM Access Analyzer、KMS 和 CloudWatch 的读取权限 — 请参阅下方的[最小权限角色](#least-privilege-iam-role-recommended)以获取确切的策略。 ``` cargo run ``` 扫描器使用您的默认 AWS profile 和区域,将人类可读的摘要打印到 stdout,并在当前目录中写入 `scanreport.json`。 **常用选项:** ``` cargo run -- --profile my-profile # use a named AWS profile cargo run -- --region eu-west-1 # override region cargo run -- --json # print JSON to stdout instead of human summary cargo run -- --verbose # show per-resource progress on stderr cargo run -- --output out.json # write report to a custom path ``` **退出代码:** | Code | 含义 | |------|---------| | `0` | PASS — 未发现问题 | | `1` | WARN — 低可信度问题 | | `2` | FAIL — 已确认的发现 | | `3` | 扫描器错误(凭证、网络等) | ## 示例输出 ## 最小权限 IAM 角色(推荐) 使用 `AdministratorAccess` 运行扫描器会导致扫描器标记自身。为避免这种情况并在生产账户中安全运行,请将以下策略附加到专用的 IAM 角色或用户,然后配置您的 AWS profile 来代入它。 扫描器仅进行只读调用。以下策略仅授予这些调用,不包含其他任何权限。 ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "STS", "Effect": "Allow", "Action": "sts:GetCallerIdentity", "Resource": "*" }, { "Sid": "S3", "Effect": "Allow", "Action": [ "s3:ListAllMyBuckets", "s3:GetBucketLocation", "s3:GetBucketPublicAccessBlock", "s3:GetAccountPublicAccessBlock", "s3:GetBucketPolicy", "s3:GetBucketPolicyStatus", "s3:GetBucketAcl", "s3:GetBucketCORS", "s3:GetBucketLogging", "s3:GetBucketVersioning", "s3:GetEncryptionConfiguration", "s3:GetBucketOwnershipControls", "s3:GetBucketObjectLockConfiguration" ], "Resource": "*" }, { "Sid": "IAM", "Effect": "Allow", "Action": [ "iam:GetAccountSummary", "iam:ListRoles", "iam:ListRolePolicies", "iam:GetRolePolicy", "iam:ListAttachedRolePolicies", "iam:GetPolicy", "iam:GetPolicyVersion", "iam:ListUsers", "iam:ListUserPolicies", "iam:GetUserPolicy", "iam:ListAttachedUserPolicies", "iam:ListAccessKeys", "iam:GetAccessKeyLastUsed", "iam:ListGroupsForUser", "iam:ListGroupPolicies", "iam:GetGroupPolicy", "iam:ListAttachedGroupPolicies" ], "Resource": "*" }, { "Sid": "GuardDuty", "Effect": "Allow", "Action": [ "guardduty:ListDetectors", "guardduty:ListFindings", "guardduty:GetFindings" ], "Resource": "*" }, { "Sid": "CloudTrail", "Effect": "Allow", "Action": [ "cloudtrail:DescribeTrails", "cloudtrail:GetTrailStatus", "cloudtrail:GetEventSelectors" ], "Resource": "*" }, { "Sid": "AccessAnalyzer", "Effect": "Allow", "Action": [ "access-analyzer:ListAnalyzers", "access-analyzer:ListFindings" ], "Resource": "*" }, { "Sid": "KMS", "Effect": "Allow", "Action": [ "kms:ListKeys", "kms:DescribeKey", "kms:ListAliases", "kms:GetKeyPolicy", "kms:GetKeyRotationStatus" ], "Resource": "*" }, { "Sid": "CloudWatch", "Effect": "Allow", "Action": [ "logs:DescribeMetricFilters", "cloudwatch:DescribeAlarms" ], "Resource": "*" } ] } ``` 将其保存为 `scanner-policy.json`,然后执行: ``` # 创建 policy aws iam create-policy \ --policy-name S3NistScannerPolicy \ --policy-document file://scanner-policy.json # 创建 role(替换 ACCOUNT_ID) aws iam create-role \ --role-name S3NistScannerRole \ --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::ACCOUNT_ID:root" }, "Action": "sts:AssumeRole" }] }' # 附加 policy aws iam attach-role-policy \ --role-name S3NistScannerRole \ --policy-arn arn:aws:iam::ACCOUNT_ID:policy/S3NistScannerPolicy ``` 然后配置 AWS profile 以代入该角色,并使用 `--profile` 传递它。使用此角色时,扫描器不会发现有风险的 IAM 主体,因此 `AdministratorAccess` 警告将永久消失。 ## 示例输出 **FAIL — 所有类别中的发现:** ``` AWS risk status: FAIL (issues found) Account : 123456789012 Scope : S3, IAM, GuardDuty, CloudTrail, AccessAnalyzer, KMS Last scan: 2026-06-17 12:00 Findings High - [ROOT CREDS] [Low] Policy:IAMUser/RootCredentialUsage (Access key ASIA0EXAMPLE0RUNWB4B) — The API ListSSHPublicKeys was invoked using root credentials. - [ROOT CREDS] [Low] Policy:IAMUser/RootCredentialUsage (Access key ASIA0EXAMPLE002R4PK2Q) — The API DescribeRegions was invoked using root credentials. - account Block Public Access — not fully enabled (missing: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets) - bucket my-pipeline-bucket — wildcard policy (principal:*, action:s3:*) - s3-nist-scanner-borderline-role — s3:Get*, s3:Put* on * [BroadReadWriteS3] (BorderlineS3Inline) Medium - 3 buckets with access logging disabled — see scanreport.json for full list - 3 buckets without HTTPS enforcement — plaintext requests are allowed - 3 buckets with versioning disabled — no protection against accidental delete or ransomware overwrite - 3 buckets with ACLs enabled — Object Ownership not set to BucketOwnerEnforced Why this matters - GuardDuty detected active findings — possible identity misuse - IAM role or user has overly broad S3 permissions — violates least-privilege - S3 bucket policies or Block Public Access expose data to unintended principals Next actions 1. Triage GuardDuty findings; quarantine affected resources 2. Scope down IAM role and user permissions 3. Enable S3 Block Public Access; review bucket policies Blind spots - Does not check S3 Access Points, MFA Delete, bucket replication destination permissions, or S3 Event notification targets - Does not detect IAM privilege escalation paths - Does not inspect SCPs, permission boundaries, or non-S3 resource-based policies - Does not distinguish SSE-KMS from SSE-S3 or validate KMS key grant permissions - Point-in-time only — does not track configuration drift or historical changes Summary Buckets : 7 GuardDuty : 2 findings IAM : 1 risky principal S3 : 1 public-like Report : scanreport.json ``` **WARN — 仅扫描器操作者噪音:** ``` AWS risk status: WARN (issues found) Account : 123456789012 Scope : S3, IAM, GuardDuty, CloudTrail, AccessAnalyzer, KMS Last scan: 2026-06-17 12:00 Findings Medium - 5 buckets with access logging disabled — see scanreport.json for full list - 5 buckets without HTTPS enforcement — plaintext requests are allowed - 5 buckets with versioning disabled — no protection against accidental delete or ransomware overwrite Info - user lab-admin — * on * (AdministratorAccess) [expected for scanner operator] Next actions 1. For production accounts, avoid running scanner with permanent AdministratorAccess Blind spots - Does not check S3 Access Points, MFA Delete, bucket replication destination permissions, or S3 Event notification targets - Does not detect IAM privilege escalation paths - Does not inspect SCPs, permission boundaries, or non-S3 resource-based policies - Does not distinguish SSE-KMS from SSE-S3 or validate KMS key grant permissions - Point-in-time only — does not track configuration drift or historical changes Summary Buckets : 5 GuardDuty : 0 findings IAM : 1 risky principal S3 : 0 public-like Report : scanreport.json ``` **PASS — 干净的账户:** ``` AWS risk status: PASS (no issues) Account : 123456789012 Scope : S3, IAM, GuardDuty, CloudTrail, AccessAnalyzer, KMS Last scan: 2026-06-17 12:00 Summary Buckets : 5 GuardDuty : 0 findings IAM : 0 risky principals S3 : 0 public-like Report : scanreport.json ``` ## CI 扫描器有一个专门的 GitHub Actions 工作流,它会运行: - build - tests - rustfmt - clippy 此工作流的作用域仅限于 `tools/s3-nist-scanner/**` 及其工作流文件下的更改。 ## JSON 报告 schema `scanreport.json` 由 [`schema.json`](schema.json) (JSON Schema 2020-12) 完整描述。该 schema 涵盖了每个字段,包括 `findings` 数组(source、resource、rule、severity、confidence、message)和 `correlations` 数组(按资源分组,按严重性排序)。 每个报告中的 `schema_version` 字段目前为 `"4"`。当输出结构发生破坏性更改时,该数字将会增加 — 消费者应拒绝具有无法识别版本的报告。 针对该报告的有用的 jq 查询: ``` # 所有高置信度发现 jq '[.findings[] | select(.confidence == "High")]' scanreport.json # 发现最多的资源(hotspots) jq '.correlations[:5]' scanreport.json # 特定 bucket 的所有发现 jq '[.findings[] | select(.resource == "bucket:my-bucket")]' scanreport.json # 根据 schema 进行验证(需要 check-jsonschema) check-jsonschema --schemafile schema.json scanreport.json ``` ## 设计说明 - **S3 策略检测** 仅标记具有通配符主体的 `Allow` 语句。仅有 `Deny` 的策略 — 例如带有 `principal:*` 的强制 SSL 语句 — 不会被视为公共暴露。 - **强制 HTTPS 检测** 检查存储桶策略中是否具有针对 `aws:SecureTransport: false` 的 `Bool` 条件的 `Deny` 效果。完全没有策略的存储桶将被标记为未强制执行。 - **对象所有权检测** 为每个存储桶调用 `GetBucketOwnershipControls`。`BucketOwnerEnforced` 表示 ACL 已禁用(无发现)。`BucketOwnerPreferred`、`ObjectWriter` 或 `OwnershipControlsNotFoundError`(未配置规则)均会使 ACL 保持活动状态并被标记。超时和未知错误将被保守处理(无发现)。 - **IAM 服务相关角色** (路径 `/aws-service-role/`) 会被跳过:它们由 AWS 管理且无法携带用户创建的内联策略。 - **扫描器操作者噪音**:如果唯一有风险的 IAM 主体是调用扫描器的 IAM 用户,则状态将从 FAIL 降级为 WARN,并且发现将标记为 `[expected for scanner operator]`。 - **GuardDuty** 使用 `service.archived = false` 进行查询,因此只会显示活跃的发现。即使存档的发现尚未从检测器中过期,也会被排除。 - **并发**:S3、IAM 和 GuardDuty 阶段并行运行(`tokio::try_join!`)。单个存储桶、IAM 主体和 GuardDuty 区域在每个阶段内也会并发运行。 ## 范围与盲区 此扫描器检查一组特定且有边界的 AWS 访问控制信号。PASS 意味着**在此范围内未发现问题** — 这并不代表该账户完全安全。 **已检查项** | 区域 | 检查 | |------|--------| | S3 | Block Public Access(账户 + 每个存储桶,全部 4 个标志),具有通配符主体的存储桶策略,ACL 公共授权,CORS 配置错误,跨账户存储桶策略授权,Object Lock(已启用版本控制的存储桶),访问日志,强制 HTTPS,版本控制,对象所有权,服务器端加密状态 | | IAM | 角色和用户的通配符与管理员策略(内联 + 附加 + 组继承),Root 访问密钥,Root MFA,超过 90 天的陈旧且从未使用的访问密钥 (CIS AWS 1.14) | | GuardDuty | 所有 17 个标准商业区域中的活跃(非存档)发现;GuardDuty 是否在每个区域启用 | | CloudTrail | 是否配置并积极记录了带有管理事件的多区域追踪 | | IAM Access Analyzer | S3、IAM、KMS、Lambda、SQS、SNS、Secrets Manager 的活跃外部访问发现(公共或跨账户);是否启用了分析器 | | KMS | 具有公共主体或向非 Root 主体授予 kms:* 权限的客户管理密钥策略;对称密钥的自动轮换状态 | | CloudWatch | 关键 CIS 警报控制(Root 使用、未授权的 API 调用、控制台登录失败、CloudTrail 更改、安全组更改、IAM 策略更改)是否存在 CloudWatch 指标过滤器;每个过滤器是否附加了警报 | **S3 盲区** - MFA Delete(要求硬件 MFA 进行版本移除的额外删除保护) - S3 Access Points 及其策略 - 存储桶复制目标账户和权限 - 限制 S3 访问的 VPC endpoint 策略 - S3 事件通知目标(Lambda、SNS、SQS)— 可能导致数据泄露 - SSE-KMS 与 SSE-S3 的区别(两者都注册为已加密) - KMS 密钥策略和密钥轮换 **IAM 盲区** - IAM 提权路径(例如 `iam:CreatePolicy` + `iam:AttachUserPolicy`) - 权限边界 - AWS Organizations 级别的 Service Control Policies (SCPs) - 身份提供商(SAML/OIDC 联合身份,`sts:AssumeRoleWithWebIdentity`) - S3 之外的基于资源的策略(Lambda、SQS、SNS、KMS、Secrets Manager、ECR 等) **GuardDuty 盲区** - 可选的保护计划(S3 Protection、EKS、Lambda、RDS、Malware Protection) **CloudTrail 盲区** - 日志文件完整性验证(CloudTrail 摘要文件) - S3 数据事件(对象级别的读/写操作) - 日志向目标 S3 存储桶的传输(存储桶是否存在、策略、加密) - AWS Organizations 中的 GuardDuty 成员账户配置 **架构盲区** - 仅限单个账户 — 不支持多账户或 AWS Organizations 遍历 - CloudTrail 启用、日志完整性和 S3 传输篡改 - AWS Config 规则、Security Hub、Macie 或 Inspector 发现 - 网络级控制(Security Groups、NACLs、VPC Flow Logs) - 嵌入在 S3 对象内容中的机密或凭证 - 历史跟踪或配置漂移 — 每次运行都是时间点快照 - 没有合规性控制映射(CIS AWS Foundations、NIST 800-53、ISO 27001) ## 计划范围 - 每个存储桶的 SSE-KMS 与 SSE-S3 强制执行 - 适用于控制审计文档的证据导出 - 多账户支持 ## 安全 如果在此项目中发现漏洞或安全问题,请私下报告,而不是开启公开的 issue。 [oscarmorberg@gmail.com](mailto:oscarmorberg@gmail.com) ## 许可证 在 MIT 许可证下授权。请参阅 `LICENSE`。
标签:AWS, DPI, LNA, Rust, 云基础设施扫描, 可视化界面, 网络流量审计, 通知系统