Repository navigation
sec(iac/aws): aws-target CloudFormation accepts the all-zeros OIDC thumbprint for any issuer #1615
Description
Reviewed commit: be11bdcb5. Note: origin/main moved to 3e9660d06 during the review; re-verify against current main before changing code, since a finding may have been fixed or moved.
Blast radius: a template customers deploy into their own AWS accounts. This is not internal CI infrastructure. The affected stack grants irreversible multi-year spend authority in the customer's account, which is why this carries priority/p0 / severity/critical / impact/all-users rather than the priority/p1 / impact/internal used for our own deploy-identity findings.
Where
iac/federation/aws-target/cloudformation/template.yaml:42-50(OIDCThumbprintparameter, default all-zeros, validated only as 40 hex characters)iac/federation/aws-target/cloudformation/template.yaml:75-76(where it is used)iac/federation/aws-target/terraform/variables.tf:79-87(the sibling guard that this template lacks)
What
The CloudFormation template accepts the all-zeros placeholder thumbprint for any issuer URL. Its only validation is an AllowedPattern requiring 40 hexadecimal characters, which the all-zeros string satisfies.
The Terraform sibling treats this as unacceptable and rejects the placeholder unless the issuer is one AWS validates natively (login.microsoftonline.com or accounts.google.com), with the reasoning recorded in the validation message:
the all-zeros value bypasses the CA-chain check entirely
Failure scenario
A customer points the CloudFormation template at a self-hosted or third-party OIDC issuer and leaves OIDCThumbprint at its default. AWS then performs no CA-chain validation on the issuer's TLS certificate. An attacker who can MITM or DNS-hijack the issuer's JWKS endpoint mints tokens that sts:AssumeRoleWithWebIdentity accepts, landing in a role that holds ec2:PurchaseReservedInstancesOffering, savingsplans:CreateSavingsPlan, and rds:PurchaseReservedDBInstancesOffering (template.yaml:96-149). The result is irreversible multi-year spend commitments in the customer's account.
The customer has no signal that anything is wrong: the stack deploys cleanly, onboarding reports success, and the difference between this and the Terraform path is invisible unless they read both.
Fix direction
Port the Terraform guard into the template. Either:
- add a
Conditionthat fails the deployment whenOIDCThumbprintequals the all-zeros value andOIDCIssuerURLis not one of the AWS-natively-validated issuers (aFn::Ifselecting a deliberately-invalid resource, or anAWS::CloudFormation::Init-style assertion), or - add a Lambda-backed custom resource that validates the thumbprint against the issuer's live certificate chain and fails the stack on mismatch.
Whichever is chosen, the all-zeros default should be removed so a customer must make an explicit choice.
Related
- sec(iac): onboarding paths drift - shell/CFN/ARM templates lack the guards their Terraform siblings enforce cloud-commitments-platform#91 (
sec(iac): onboarding paths drift) lists this as instance 4 of the cross-cutting pattern. That issue tracks the missing parity mechanism (CI parity tests across onboarding paths); this issue tracks the individual fix to this template, which is needed regardless of whether the parity mechanism lands. - sec(iac): aws-target CloudFormation makes the OIDC sub restriction optional; the default trusts every identity from the issuer #1543 covers the sibling defect in the same file:
OIDCSubjectClaimdefaulting to empty so the trust policy has no:subcondition. Both defects are in the same trust policy and should be fixed in one pass, but they are independent (fixing one leaves the other exploitable).
Activity
sec(iac/aws): align aws-target Terraform thumbprint guard with AWS be…
docs(iac/aws): qualify the thumbprint fallback claim and tighten the …
cristim commentedon Aug 3, 2026
Re-triage: p0/critical → p2/medium
An independent adversarial review of PR #1678 tested this issue's threat model against current AWS documentation (both pages fetched and quoted verbatim, not paraphrased). The mechanism this issue describes does not exist, so the severity is being corrected. The fix in #1678 is still worth landing — see below.
What the issue claims
What AWS actually documents
From Obtain the thumbprint for an OIDC identity provider:
So the thumbprint value cannot disable CA-chain validation:
AssumeRoleWithWebIdentityfails. It fails closed.There is no documented sentinel meaning for an all-zeros value, and even if it were treated as "no thumbprint" that degrades to the omitted case, where IAM retrieves the real thumbprint. Neither is a bypass.
The MITM/DNS-hijack attacker in the issue's scenario must still present a certificate signed by a CA in AWS's trusted-root library, and that bar does not move with this parameter. There is therefore no path from this defect to assuming the commitment-purchasing role.
Note also that the AWS page names no IdPs — the older "Auth0, GitHub, GitLab, Google, S3-hosted JWKS" phrasing that the Terraform module's rationale rests on is outdated. The module comment "the all-zeros value bypasses the CA-chain check entirely" describes a mechanism that does not exist.
What the real defect is
That is a genuine foot-gun on a customer-deployed template and worth fixing — but it is not a takeover risk, so
severity/criticalandimpact/all-usersoverstate it.Confirmed accurate
The issue's statement that the parameter is "validated only as 40 hexadecimal characters" is correct —
origin/maincarriesAllowedPattern: "^[0-9a-fA-F]{40}$". Only the mechanism claim is wrong.Label change
priority/p0 → p2,severity/critical → medium,urgency/now → this-sprint,impact/all-users → few. Keepingtype/security— the misleading-documentation aspect is security-adjacent even though the exploit is not real.Leaving the original issue text intact rather than editing it; this comment is the correction of record. Flagging for the same reason to anyone working LeanerCloud/cloud-commitments-platform#91 or #1543, which may have inherited the same model.
cristim commentedon Aug 3, 2026
Closed by PR #1678 — verification record
Auto-closed by the
Closeskeyword. Recording the verification, since this issue was also re-triaged p0→p2 (see the earlier comment) and the two decisions rest on the same evidence.Independent adversarial review (reviewer distinct from the implementer), three reports:
Threat model — the issue's mechanism claim was wrong, the fix is still worth landing. Both AWS pages were fetched independently rather than trusting the PR's quotes. The thumbprint cannot disable CA-chain validation: on the primary path it is not consulted at all, and on the fallback path all-zeros matches no certificate, so authentication fails closed. Details in the re-triage comment above.
The
AllowedPatternis not a tautology. Enumerated over the domain: no all-zero string of any length 1-41 is accepted; the effective accepted set is empty, or 1-40 hex characters with at least one non-zero. The #1602 failure shape (a guard rejecting the harmless value while admitting the fail-open one) cannot recur here, because a thumbprint is a comparison target for a certificate fingerprint and has no expansion semantics — there is no fail-open value for a pattern to wrongly admit.The two validation layers agree.
Rulesrejects a strict subset of whatAllowedPatternrejects — zero contradictions.Rulesis fully redundant today and deliberately so: it survives a future "simplification" of the pattern. Unconditional-rule and evaluated-before-any-resource semantics confirmed against AWS docs; the YAML nesting matches AWS's own documented example.AWS::NoValuewiring is correct. Empty resolves the condition false and the property is omitted, not sent as an empty list — the documented idiom.ThumbprintListconfirmedRequired: NowithUpdate requires: No interruption, so switching forms never replaces the provider or changes its ARN.Terraform/CloudFormation parity restored on the security-relevant rule, exercised against the real
terraformbinary with the variable block extracted verbatim. It also closed a gap the PR did not claim: the old guard waslength(list) == 1 && list[0] == Z, so["<real-thumbprint>", "0000…"]slipped through for any issuer. The newalltrue(...)catches the placeholder in any position. Two divergences remain, both fail-closed and one documented: CFN accepts 1-39 hex where the IAM API rejects at create time (TF rejects at plan), and cardinality (CFN single value vs TF list).Tests genuinely bite. Verified by swapping
origin/main's template into the extract and re-running each with-count=1: both FAIL pre-fix, both pass on the head, extract restored and sha256-verified. They parse pattern/MaxLength/Default back out of the committed template rather than retyping them, scoped to the named parameter — which matters, because the reviewer's own first pass used a naive whole-file regex and silently picked upOIDCAudience's pattern instead.Upgrade path is deliberate and non-regressing. Existing stacks holding the placeholder fail their next update until the parameter is cleared (
--parameter-overrides "OIDCThumbprint="); such a stack is either on the primary path where the value is unread, or the fallback path where it cannot be authenticating today. Generated bundles pick up the new empty default with no newly-required parameter.Known gap left open after this merge
internal/iacfiles/templates/aws-wif-cli.sh.tmplstill hardcodes the placeholder — tracked as #1689. Until that lands, the CLI bundle is the only remaining path shipping it (drift reduced from three-way to two-versus-one, not to zero).Also flagged, unverifiable without a live stack: the PR documents that Terraform's
thumbprint_listis Optional+Computed so clearing it does not clear a value already stored on an existing provider, and gives the out-of-band remedy — but offers no equivalent caveat for CloudFormation. The sameaws iam update-open-id-connect-provider-thumbprintcommand applies to both.