Skip to content

sec(iac/aws): aws-target CloudFormation accepts the all-zeros OIDC thumbprint for any issuer #1615

Closed

Description

@cristim

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 (OIDCThumbprint parameter, 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 Condition that fails the deployment when OIDCThumbprint equals the all-zeros value and OIDCIssuerURL is not one of the AWS-natively-validated issuers (a Fn::If selecting a deliberately-invalid resource, or an AWS::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

Activity

  1. cristim commented on Aug 3, 2026

    @cristim
    MemberAuthor

    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

    AWS then performs no CA-chain validation on the issuer's TLS certificate

    What AWS actually documents

    From Obtain the thumbprint for an OIDC identity provider:

    AWS secures communication with OIDC identity providers (IdPs) using our library of trusted root certificate authorities (CAs) to verify the JSON Web Key Set (JWKS) endpoint's TLS certificate. If your OIDC IdP relies on a certificate that is not signed by one of these trusted CAs, only then we secure communication using the thumbprints set in the IdP's configuration. AWS will fall back to thumbprint verification if we are unable to retrieve the TLS certificate or if TLS v1.3 is required.

    So the thumbprint value cannot disable CA-chain validation:

    • Primary path (JWKS certificate chains to a trusted root — both documented issuers, and any realistic hosted IdP): the thumbprint is not consulted at all. All-zeros, a correct thumbprint, and an omitted list are indistinguishable. The CA-chain check runs regardless.
    • Fallback path (private CA, certificate unretrievable, or TLS 1.3 required): the thumbprint is consulted — and all-zeros is not the SHA-1 of any certificate, so it matches nothing and every AssumeRoleWithWebIdentity fails. 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

    1. Availability — on the fallback path the shipped default guarantees auth failure. It converts "verify" into "always fail".
    2. Misleading documentation — the current text ("Leave as zeros only for well-known providers where AWS auto-validates the chain") tells operators the value matters for other issuers, when it is unread for every publicly-trusted issuer and actively breaks private-CA ones.
    3. No way to express AWS's own default (let IAM retrieve the thumbprint).

    That is a genuine foot-gun on a customer-deployed template and worth fixing — but it is not a takeover risk, so severity/critical and impact/all-users overstate it.

    Confirmed accurate

    The issue's statement that the parameter is "validated only as 40 hexadecimal characters" is correct — origin/main carries AllowedPattern: "^[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. Keeping type/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.

  2. cristim commented on Aug 3, 2026

    @cristim
    MemberAuthor

    Closed by PR #1678 — verification record

    Auto-closed by the Closes keyword. 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 AllowedPattern is 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. Rules rejects a strict subset of what AllowedPattern rejects — zero contradictions. Rules is 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::NoValue wiring is correct. Empty resolves the condition false and the property is omitted, not sent as an empty list — the documented idiom. ThumbprintList confirmed Required: No with Update 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 terraform binary with the variable block extracted verbatim. It also closed a gap the PR did not claim: the old guard was length(list) == 1 && list[0] == Z, so ["<real-thumbprint>", "0000…"] slipped through for any issuer. The new alltrue(...) 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 up OIDCAudience'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.tmpl still 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_list is 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 same aws iam update-open-id-connect-provider-thumbprint command applies to both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions