← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2024-28056 · CWE-276 · Disclosed 2024-04-15

Amazon AWS Amplify CLI before 12

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is less a front-door RCE and more a forgotten master key left in a retired lockbox

CVE-2024-28056 affects AWS Amplify CLI versions before 12.10.1. The bug appears when an Amplify project had its Authentication component removed: the CLI deleted the trust-policy Condition but left "Effect":"Allow", leaving legacy authRole and unauthRole IAM roles assumable through Cognito. Per AWS and Datadog, the risky state mainly applies to projects where auth was removed from apps built between August 2019 and January 2024; the fixed CLI shipped on January 10, 2024.

The vendor's 9.8/CRITICAL rating overstates today's enterprise risk. That score models a clean unauthenticated network path, but AWS subsequently changed IAM and STS in late February and April 2024 to block the straightforward cross-account abuse path. The residual risk is real, but it now depends on historical project state, left-behind vulnerable roles, and typically a same-account Cognito identity pool with classic authflow enabled. That's a meaningful downgrade in reachable population.

"Vendor called it internet-critical; in practice it's a narrow legacy IAM state bug with a same-account escape hatch"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Legacy Amplify role is created in a bad state

The vulnerable condition is not 'running an old CLI today'; it's that an older Amplify workflow previously removed auth and rewrote the IAM trust policy incorrectly. The role keeps sts:AssumeRoleWithWebIdentity for cognito-identity.amazonaws.com but loses the aud scoping condition that should bind it to one identity pool.
Conditions required:
  • Amplify CLI version earlier than 12.10.1 was used historically
  • An Amplify project's Authentication component was removed
  • The resulting IAM role still exists
Where this breaks in practice:
  • This only hits a subset of Amplify users, not every install
  • It requires a specific lifecycle event, not just package presence
  • Many orgs never removed Amplify auth at all
Detection/coverage: SCA will flag the CLI version, but that alone does not prove exploitable cloud state. You need IAM trust-policy inspection.
STEP 02

Attacker finds a usable role ARN and same-account Cognito path

The attacker needs the vulnerable role ARN and, after AWS's service-side mitigations, a Cognito identity pool in the same AWS account with classic/basic authflow enabled. Datadog showed role ARNs are often discoverable from public repos and code search, but the same-account requirement sharply narrows the population compared with the original cross-account path.
Conditions required:
  • Knowledge of the target role ARN
  • At least one Cognito identity pool in the victim account with AllowClassicFlow enabled
Where this breaks in practice:
  • AWS blocked the clean cross-account route in STS
  • Shodan/Censys-style internet scanning is mostly useless here because this is IAM/Cognito control-plane state
  • Classic authflow is not universal in modern Cognito deployments
Detection/coverage: Cloud posture tools can detect permissive Cognito trust policies and classic authflow on identity pools; perimeter scanners generally cannot.
STEP 03

Attacker mints a Cognito token

Using AWS CLI or equivalent tooling, the attacker creates or uses a victim-account identity in Cognito and obtains an OpenID token. If the vulnerable role only checks amr=authenticated, the attacker needs to authenticate through a compatible user pool; if the trust policy has no aud scoping, the token can still be exchanged against the wrong role.
Conditions required:
  • Victim identity pool supports classic flow
  • For some variants, attacker can complete an authenticated Cognito flow
Where this breaks in practice:
  • Authenticated variants add setup and often require access to a working login path
  • Broken or retired Cognito configs can make exploitation fail in practice
Detection/coverage: Look for cognito-identity:GetId, GetOpenIdToken, and unusual identity-pool usage preceding STS role assumption.
STEP 04

STS issues creds for the legacy role

The final move is sts:AssumeRoleWithWebIdentity against the leftover Amplify role. If successful, the attacker receives short-lived credentials and can act with whatever privileges the legacy authRole or unauthRole still holds, which can range from low-value app access to meaningful lateral movement in the AWS account.
Conditions required:
  • Role trust policy remains permissive
  • STS still accepts the same-account token-to-role chain
  • Role policy grants useful permissions
Where this breaks in practice:
  • Blast radius is bounded by the role's actual permissions
  • Well-scoped legacy roles may only expose one app's resources
  • CloudTrail often records the key API calls needed for investigation
Detection/coverage: CloudTrail should capture AssumeRoleWithWebIdentity; detections are strongest when you alert on Amplify-style role names or Cognito-trusted roles being assumed unexpectedly.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo CISA KEV entry found, and I found no authoritative reporting of active campaigns abusing this CVE at scale. Public research exists, but not confirmed broad exploitation.
Public exploit guidanceYes. Datadog published original research and attack details, and *Hacking the Cloud* documented a same-account exploitation path after AWS blocked cross-account abuse.
EPSS0.00648 (~0.648%) from the prompt intel; that is low and consistent with a niche, environment-dependent attack path. I did not verify a primary-source percentile.
KEV statusNot listed in the CISA Known Exploited Vulnerabilities catalog.
CVSS vector reality checkCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H assumes a clean remote path. Post-mitigation reality is narrower: historical state + legacy IAM role + same-account Cognito classic flow.
Affected rangeAmplify CLI < 12.10.1. Practically, exposure is tied to projects where the Authentication component was removed from apps built between 2019-08 and 2024-01.
Fixed versions and platform mitigationsCLI fixed in 12.10.1 on 2024-01-10. AWS says Amplify Studio was also fixed in January 2024; Datadog reports later STS/IAM changes in February and April 2024 blocked the broad cross-account path.
Exposure dataThis is not a Shodan/Censys problem. Datadog instead used public code search and collected 8,000+ role ARNs in minutes, showing role-name discoverability is easy even though internet surface measurement is weak.
Disclosure and researcherCoordinated disclosure landed 2024-04-15. Research was published by Nick Frichette / Datadog Security Labs.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.4/10)

The decisive factor is that AWS killed the easy cross-account exploit path, leaving a materially narrower same-account chain. What remains is a legacy cloud-state issue that usually requires additional Cognito conditions, so it is not a fleet-wide internet-critical event anymore.

HIGH Downgrade from vendor CRITICAL to a materially lower real-world priority
MEDIUM Residual same-account exploitability in a given AWS account depends on identity-pool configuration and legacy role permissions

Why this verdict

  • Cross-account path is dead: AWS added STS protections in late February and April 2024 that block the original broad abuse path described by the CVSS story.
  • Historical-state requirement cuts population hard: you only care if an older Amplify workflow removed auth and left behind a permissive Cognito-trusted IAM role.
  • Residual exploit needs a second condition: practical abuse now typically requires a same-account Cognito identity pool with classic/basic authflow enabled, not just a vulnerable package version.
  • Impact can still be meaningful: if the leftover role is privileged, successful AssumeRoleWithWebIdentity becomes AWS-account lateral movement, which keeps this above LOW.

Why not higher?

This is not a modern pre-auth internet smash-and-grab anymore. The clean remote path the vendor score implies has been constrained by AWS service-side changes, and the remaining chain depends on uncommon legacy configuration plus additional Cognito prerequisites.

Why not lower?

I would not drop this to LOW because the leftover IAM roles can still carry real privileges, and public role-ARN discovery is easier than many teams assume. If your account still has permissive Amplify roles and classic-flow identity pools, this becomes a practical privilege-escalation path.

05 · Compensating Control

What to do — in priority order.

  1. Audit Cognito-trusted IAM roles — Search for roles that allow cognito-identity.amazonaws.com to call sts:AssumeRoleWithWebIdentity without an aud condition. For a MEDIUM noisgate verdict there is no mitigation SLA; do this as part of the normal 365-day remediation window, but move faster if you find legacy Amplify authRole or unauthRole names.
  2. Disable classic authflow on identity pools — Turn off Cognito basic/classic authflow where applications do not explicitly need it. That strips out the most important residual exploit enabler; for this MEDIUM case there is no mitigation SLA, so handle it in the normal remediation cycle unless you confirm a vulnerable role exists.
  3. Prune or re-scope legacy Amplify roles — Delete orphaned Amplify roles or reduce them to least privilege if the app still depends on them. This cuts blast radius even if a trust-policy issue slipped through; again, no mitigation SLA applies here, so fold it into the 365-day remediation plan.
  4. Alert on web-identity role assumptions — Create CloudTrail detections for AssumeRoleWithWebIdentity against Cognito-trusted roles, especially Amplify-style role names. This gives you high-signal visibility into the exact final hop of the attack chain during the normal remediation window.
What doesn't work
  • Just upgrading the developer CLI does not repair already-created IAM roles; the bug leaves behind cloud state.
  • Perimeter scanning, WAFs, and external attack-surface tools won't tell you much here because the exposure is in AWS IAM/Cognito trust relationships, not a public web endpoint.
  • MFA on your human AWS admins is good hygiene but does not directly stop AssumeRoleWithWebIdentity on a permissive Cognito-trusted role.
06 · Verification

Crowdsourced verification payload.

Run this from an auditor workstation, AWS CloudShell, or CI job with boto3 available and read permissions to IAM, Cognito Identity, and optionally STS/EC2 region discovery. Invoke it as python3 check_cve_2024_28056.py --profile prod or with ambient credentials; it needs read-only AWS API access, not shell access to any host.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# check_cve_2024_28056.py
# Exit codes:
#   0 = PATCHED (no vulnerable roles found)
#   1 = VULNERABLE (vulnerable role(s) + same-account classic-flow identity pool(s) found)
#   2 = UNKNOWN (insufficient permissions/errors, or vulnerable roles found but no classic-flow pool found)

import argparse
import sys
import json
from botocore.exceptions import BotoCoreError, ClientError

try:
    import boto3
except Exception:
    print('UNKNOWN - boto3 is not installed')
    sys.exit(2)


def make_session(profile=None):
    try:
        return boto3.Session(profile_name=profile) if profile else boto3.Session()
    except Exception as e:
        print(f'UNKNOWN - unable to create boto3 session: {e}')
        sys.exit(2)


def normalize_actions(action):
    if isinstance(action, list):
        return action
    if isinstance(action, str):
        return [action]
    return []


def is_cognito_trust_vulnerable(stmt):
    if stmt.get('Effect') != 'Allow':
        return False

    principal = stmt.get('Principal', {})
    federated = principal.get('Federated')
    if federated != 'cognito-identity.amazonaws.com':
        return False

    actions = normalize_actions(stmt.get('Action'))
    if 'sts:AssumeRoleWithWebIdentity' not in actions:
        return False

    # Vulnerable pattern for this CVE family: missing aud scoping condition.
    cond = stmt.get('Condition')
    if not cond:
        return True

    string_equals = cond.get('StringEquals', {}) if isinstance(cond, dict) else {}
    aud = string_equals.get('cognito-identity.amazonaws.com:aud')
    if not aud:
        return True

    return False


def list_vulnerable_roles(session):
    iam = session.client('iam')
    vulnerable = []
    paginator = iam.get_paginator('list_roles')
    for page in paginator.paginate():
        for role in page.get('Roles', []):
            doc = role.get('AssumeRolePolicyDocument', {})
            statements = doc.get('Statement', [])
            if isinstance(statements, dict):
                statements = [statements]
            for stmt in statements:
                if is_cognito_trust_vulnerable(stmt):
                    vulnerable.append({
                        'RoleName': role.get('RoleName'),
                        'Arn': role.get('Arn')
                    })
                    break
    return vulnerable


def list_classic_flow_pools(session):
    regions = session.get_available_regions('cognito-identity')
    found = []
    errors = []

    for region in regions:
        try:
            client = session.client('cognito-identity', region_name=region)
            token = None
            while True:
                kwargs = {'MaxResults': 60}
                if token:
                    kwargs['NextToken'] = token
                resp = client.list_identity_pools(**kwargs)
                for pool in resp.get('IdentityPools', []):
                    pool_id = pool['IdentityPoolId']
                    desc = client.describe_identity_pool(IdentityPoolId=pool_id)
                    if desc.get('AllowClassicFlow') is True:
                        found.append({
                            'Region': region,
                            'IdentityPoolId': pool_id,
                            'IdentityPoolName': desc.get('IdentityPoolName', '')
                        })
                token = resp.get('NextToken')
                if not token:
                    break
        except (ClientError, BotoCoreError) as e:
            errors.append(f'{region}: {e}')
        except Exception as e:
            errors.append(f'{region}: {e}')

    return found, errors


def main():
    parser = argparse.ArgumentParser(description='Assess residual exposure for CVE-2024-28056 in an AWS account')
    parser.add_argument('--profile', help='AWS profile name', default=None)
    args = parser.parse_args()

    session = make_session(args.profile)

    try:
        vulnerable_roles = list_vulnerable_roles(session)
    except (ClientError, BotoCoreError) as e:
        print(f'UNKNOWN - IAM enumeration failed: {e}')
        sys.exit(2)
    except Exception as e:
        print(f'UNKNOWN - unexpected IAM error: {e}')
        sys.exit(2)

    try:
        classic_pools, pool_errors = list_classic_flow_pools(session)
    except Exception as e:
        print(f'UNKNOWN - Cognito enumeration failed: {e}')
        sys.exit(2)

    summary = {
        'vulnerable_role_count': len(vulnerable_roles),
        'classic_flow_pool_count': len(classic_pools),
        'example_roles': vulnerable_roles[:5],
        'example_classic_flow_pools': classic_pools[:5],
        'regional_errors': pool_errors[:10],
    }

    if vulnerable_roles and classic_pools:
        print('VULNERABLE - permissive Cognito-trusted role(s) and same-account classic-flow identity pool(s) found')
        print(json.dumps(summary, indent=2, sort_keys=True))
        sys.exit(1)

    if vulnerable_roles and not classic_pools:
        print('UNKNOWN - vulnerable role(s) found, but no classic-flow identity pools were discovered')
        print('UNKNOWN - cross-account abuse is blocked by AWS, but stale role trust policies still need review')
        print(json.dumps(summary, indent=2, sort_keys=True))
        sys.exit(2)

    if not vulnerable_roles:
        print('PATCHED - no vulnerable Cognito-trusted IAM role patterns found for CVE-2024-28056')
        print(json.dumps(summary, indent=2, sort_keys=True))
        sys.exit(0)

    print('UNKNOWN - unable to determine state conclusively')
    print(json.dumps(summary, indent=2, sort_keys=True))
    sys.exit(2)


if __name__ == '__main__':
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, treat this as a cloud-state audit, not a panic patch drill. For a MEDIUM verdict, there is no noisgate mitigation SLA — go straight to the 365-day remediation window under the noisgate remediation SLA: upgrade any remaining Amplify CLI installs to 12.10.1+, then use IAM/Cognito inspection to find and fix leftover vulnerable Amplify roles and disable classic authflow where it is not needed. If your audit finds both permissive Cognito-trusted roles and same-account classic-flow identity pools, don't wait for the full year—clean those specific accounts up in the next change cycle because that is the residual practical exploit path.

Sources

  1. AWS Security Bulletin AWS-2024-003
  2. NVD CVE-2024-28056
  3. Amplify CLI v12.10.1 release
  4. Fix commit 73b08dc
  5. Datadog Security Labs research
  6. Hacking the Cloud same-account exploitation write-up
  7. AWS Cognito identity pools authentication flow
  8. CISA KEV catalog
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.