Skip to content

feat(entries): add Entra directory-role and AWS IAM privesc pairs - #63

Merged
Gerrrt merged 2 commits into
mainfrom
claude/github-issues-query-5m5z5o
Aug 1, 2026
Merged

feat(entries): add Entra directory-role and AWS IAM privesc pairs#63
Gerrrt merged 2 commits into
mainfrom
claude/github-issues-query-5m5z5o

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Closes #62.

Two new red↔blue pairs, both T1098.003. The issue framed these as a prioritization call rather than a defect — nothing in the corpus is wrong — so this PR is that call being made: add them. Each is the direct analogue of a pair that already exists for the neighbouring platform, which is what made the holes visible in the first place.

Entra privileged directory-role grant

entries/red/entra-directory-role.mdentries/blue/entra-role-assign-audit.md

Google Workspace super-admin was covered end-to-end (gws-super-admingws-admin-audit) while the Entra twin was not. Entra was well covered on the app plane (consent-grant, device-code, sp-cred-backdoor) but had nothing on the directory-role plane.

Two things I went beyond the issue's sketch on, because leaving them out would ship a detection with a hole in it:

  • The blue half covers the PIM variant. The issue specified keying on Add member to role. But under PIM an eligible assignment writes Add eligible member to role — a standing backdoor that stays dormant until activated. A tenant watching only the direct grant sees it at use time, not at plant time, which is the wrong half of the timeline. Both are matched.
  • The role name is read from Role.DisplayName, a modified property on the target, not from the top-level event. The naive query returns every role grant in the tenant and gets tuned into uselessness.

Red flags Privileged Authentication Administrator alongside GA/PRA: it can reset a Global Admin's credentials, so it's a GA grant one step removed, and it's the one most likely to be waved through on review. Blue says to treat it as equal severity.

AWS IAM privilege escalation

entries/red/aws-iam-privesc-policy.mdentries/blue/aws-iam-privesc-cloudtrail.md

GCP had gcp-iam-policy-backdoor; AWS covered console / access-key / S3 / destroy but not the escalation itself.

The attack has two shapes and both are covered, because a detection for only the first is trivially evaded by the second:

  • The AttachUserPolicy self-grant — actor equals target, or the policy is wildcard-admin. Blue also matches CreatePolicyVersion --set-as-default, which is the same escalation wearing an update's clothes, and PowerUserAccess next to AdministratorAccess, since an ARN-string match on the latter alone misses it.
  • The iam:PassRole path, which never touches the actor's own IAM identity — pass a privileged role to an EC2 instance or Lambda you control and read the credentials out of IMDS or the function environment. Blue's guidance is to alert on a PassRole by a principal that doesn't normally launch compute, then pivot on whether the passed role's credentials show up from a new source IP.

Checks

Full CI run locally, all green:

  • shellcheck -S warning + bash -n on all four scripts
  • ./gen-views.sh --check — the drift gate (standalone, no flat-view targets present)
  • the pairing-graph walk — both new pairs resolve bidirectionally
  • slot coverage — no {{slots}} used; both are cloud entries and follow the existing <angle-bracket> convention, matching gws-super-admin / gcp-iam-policy-backdoor
  • ./htpx --preview on both red entries, confirming each renders with its paired detection

Sections (Microsoft 365 / Entra ID, AWS / cloud IAM) and the detection: backends (kql-entra-audit, splunk-cloudtrail) are existing values — no new taxonomy.

Note for review

The detection queries are written from the documented shape of each audit schema, not validated against a live tenant or CloudTrail estate. The invariants they key on are the stable part; the field paths are where I'd want a second pair of eyes if you have a tenant to check them against. That's the same standing caveat as the rest of the corpus, but these two are query-heavy enough to be worth flagging.

This is [Unreleased] only — no version cut, so no fan-out to dotfiles-Kali until a release is tagged.


Generated by Claude Code

Closes the two cloud-IdP gaps from the corpus-review cycle. Both were
asymmetries rather than absences: each is the direct analogue of a pair
that already existed for the neighbouring platform.

Entra privileged directory-role grant (T1098.003) — the twin of the
covered Google Workspace super-admin pair. Entra was well covered on the
app plane (consent-grant, device-code, sp-cred-backdoor) but had nothing
on the directory-role plane. Blue keys on `Add member to role`, reads the
role from the Role.DisplayName modified property rather than the
top-level event, and covers the PIM `Add eligible member to role`
variant — without which a standing backdoor stays invisible until it is
activated.

AWS IAM privilege escalation (T1098.003) — GCP had
gcp-iam-policy-backdoor; AWS covered console, access-key, S3 and destroy
but not the escalation. Red covers both shapes: the AttachUserPolicy
self-grant and the iam:PassRole path that never touches the actor's own
identity. Blue matches both, plus CreatePolicyVersion --set-as-default
and PowerUserAccess, which the obvious AdministratorAccess-only query
misses.

Closes #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzKmTKtvGgsa82MCxPPy5i
Copilot AI review requested due to automatic review settings August 1, 2026 22:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new cloud red↔blue pairs to close parity gaps identified in #62: Entra directory-role persistence and AWS IAM privilege-escalation paths, aligning coverage with existing Google Workspace and GCP analogues.

Changes:

  • Add Entra privileged directory-role grant red entry and its Entra AuditLogs (KQL) detection pair (T1098.003).
  • Add AWS IAM privilege escalation red entry and its CloudTrail (Splunk) detection pair (T1098.003).
  • Document the additions under [Unreleased] in CHANGELOG.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
entries/red/entra-directory-role.md New red entry describing Entra privileged directory-role assignment as tenant persistence.
entries/blue/entra-role-assign-audit.md New blue entry with KQL over Entra AuditLogs for directory-role grants (incl. PIM eligibility).
entries/red/aws-iam-privesc-policy.md New red entry describing AWS IAM privesc via AttachUserPolicy and PassRole→AssumeRole paths.
entries/blue/aws-iam-privesc-cloudtrail.md New blue entry with Splunk CloudTrail query for IAM privesc-related activity.
CHANGELOG.md Adds [Unreleased] changelog bullets documenting the two new pairs and rationale.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +39
index=aws sourcetype=aws:cloudtrail
eventName IN (AttachUserPolicy, AttachRolePolicy, AttachGroupPolicy, PutUserPolicy, CreatePolicyVersion)
| eval actor=coalesce('userIdentity.userName','userIdentity.arn','userIdentity.principalId')
| eval target=coalesce('requestParameters.userName','requestParameters.roleName','requestParameters.policyArn')
| where like('requestParameters.policyArn',"%AdministratorAccess%")
OR like('requestParameters.policyArn',"%PowerUserAccess%")
OR 'requestParameters.setAsDefault'="true"
OR actor=target
| table _time, eventName, actor, target, requestParameters.policyArn, sourceIPAddress, userAgent

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, and this was the worst kind of gap to ship — a detection entry whose prose tells you to watch something the query can't see. Fixed in 9d74a02 with a second block, following the asrep-probing-4771 convention (primary query, prose lead-in, secondary query) so the renderer keeps both.

Writing it surfaced something better than the missing query. There is no PassRole CloudTrail event at alliam:PassRole is an authorization check evaluated during the launching call, not an API call of its own. So the failure mode isn't just "this query misses that path": a defender who goes looking for an event by that name finds nothing and reasonably concludes there's nothing to detect. The passed role exists only as a request parameter on the RunInstances / CreateFunction / RegisterTaskDefinition that consumed it. The prose now states that outright, and the second query keys on requestParameters.iamInstanceProfile.arn / role / roleArn / taskRoleArn with a baseline-then-outlier shape rather than a fixed threshold.

Drift gate, pairing graph, shellcheck and htpx --preview all re-run green; CHANGELOG updated to match.


Generated by Claude Code

…tion

The entry's prose described two escalation shapes but shipped only the
self-grant query, so the PassRole half it told defenders to watch had
nothing to run.

Adds it as a second block, following the asrep-probing-4771 convention.
The gap was worth more than a copy-paste: iam:PassRole is an
authorization check on the launching call, NOT an API call, so there is
no PassRole event to search for at all — the role appears only in the
requestParameters of the RunInstances/CreateFunction that consumed it.
A defender hunting an event by that name finds nothing and concludes
they are covered. The prose now says so explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzKmTKtvGgsa82MCxPPy5i
@Gerrrt
Gerrrt merged commit 2ee7a84 into main Aug 1, 2026
1 check passed
@Gerrrt
Gerrrt deleted the claude/github-issues-query-5m5z5o branch August 1, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coverage gap: no Entra directory-role escalation pair (asymmetric with GWS), and no AWS IAM priv-esc pair (asymmetric with GCP)

3 participants