The New PKCE Authentication in AWS SSO Brings Hope (Mostly)

In 2021, I wrote about how offensive actors can leverage AWS SSO device code for phishing, rendering modern security controls like FIDO authentication or identity provider device posture ineffective: Phishing for AWS credentials via AWS SSO device code authentication. In this post, we’ll take a closer look at the newly-released PKCE support for AWS SSO authentication flows.

A Short History of Device Code Phishing

As highlighted in the original article, Device Code phishing isn’t new or specific to AWS. In fact, it had previously been demonstrated in the context of Azure AD. However, following the publication, the technique gained notable interest within the community:

  • Sebastian Mora authored a blog post and released “awsssome_phish“, an offensive tool designed to operationalize device code phishing attacks.
  • Chaim Sanders pushed an article discussing the risks associated with device code phishing within the AWS ecosystem
  • Matthew Garrett made the point that device code authentication effectively bypasses many of the advanced security mechanisms implemented in recent years.
  • Rami McCarthy published a great opinionated guide outlining potential steps AWS should take to address this issue.
  • Chris Norman released an open-source browser extension that can protect users against device code phishing attacks.

While I’m not aware of reports about in-the-wild exploitation, I’ve heard direct feedback from at least two red teams that device code phishing is extremely effective.

re:Invent 2024—a New Hope

A few days before re:Invent, AWS announced the release of “PKCE-based Authorization for SSO“, which is now available and enabled by default in AWS CLI version 2.22.0 and later. While the term PKCE (Proof Key for Code Exchange) might sound obscure, it is essentially an implementation of the standard OAuth 2.0 Authorization Code Flow.

The new UI for the PKCE-based authentication flow

Here’s a sequence diagram that conceptually describes the flow:

This flow fundamentally differs from device code authentication in that, after successful authentication, AWS redirects the user to localhost. Here, the client application (in this case, the AWS CLI) retrieves the authorization code by spinning up a temporary web server. The key implication is that an attacker creating a malicious authentication link cannot retrieve the authorization token to impersonate the victim. Even if the victim successfully authenticates through AWS SSO, they will be redirected to localhost and encounter a browser error, rendering the attack ineffective.

Crazy how these phishing websites never work!

How does the implementation work in detail?

The implementation of the AWS CLI should give you a good idea (more specifically the SSOTokenFetcherAuth class). I have also reproduced a naive and ugly Python implementation below.

▶ Sample Python implementation (click to expand)
"""
Note: you need a recent boto3 version. Make sure to replace your SSO start URL below too.
"""

SSO_START_URL='https://d-1234.awsapps.com/start'

import boto3
import jwt
import json
import urllib.parse
import secrets
import base64
import hashlib


def generate_code_verifier_and_challenge():
    # Generate a cryptographically random code_verifier
    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode('utf-8')
    
    # Compute the code_challenge from the code_verifier
    digest = hashlib.sha256(code_verifier.encode('utf-8')).digest()
    code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('utf-8')
    
    return code_verifier, code_challenge

sso_oidc = client = boto3.client('sso-oidc')
redirect_uri = "http://127.0.0.1"

response = client.register_client(
    clientName='pkce-demo',
    clientType='PUBLIC',
    scopes=["sso:account:access"],
    redirectUris=[
       redirect_uri
    ],
    issuerUrl=SSO_START_URL,
    grantTypes=["authorization_code", "refresh_token"],
)

client_id = response['clientId']
client_secret = response['clientSecret']
code_verifier, code_challenge = generate_code_verifier_and_challenge()

url = "https://oidc.us-east-1.amazonaws.com/authorize?response_type=code"
url += f"&client_id={client_id}"
url += f"&redirect_uri={urllib.parse.quote(redirect_uri)}"
url += "&state=dontcare"
url += "&code_challenge_method=S256"
url += "&scopes=sso%3Aaccount%3Aaccess"
url += f"&code_challenge={code_challenge}"



# Decode the JWT in client_secret without validating it
claims = jwt.decode(client_secret, options={"verify_signature": False})

# Read token interactively
print("Please open this URL in your browser:") 
print(url)
print()
print("When you get redirected to localhost, paste the OAuth code below:")
code = input("OAuth code: ")

response = client.create_token(
    grantType='authorization_code',
    clientId=client_id,
    clientSecret=client_secret,
    redirectUri=redirect_uri,
    codeVerifier=code_verifier,
    code=code
)
access_token = response['accessToken']

print("Successfully retrieved an AWS SSO access token.")
print("Available AWS accounts:")
sso_client = boto3.client('sso')
response = sso_client.list_accounts(accessToken=access_token)
for account in response.get('accountList', []):
    print(f" - {account['accountName']} ({account['accountId']})")

"""
Now generate some creds!
response = sso_client.get_role_credentials(
    roleName='account-admin',
    accountId='111111',
    accessToken=access_token
)
print(response)
"""

Sample output:

Please open this URL in your browser:
https://oidc.us-east-1.amazonaws.com/authorize?response_type=code&client_id=icBsllQ8y8kJWfyMjKH3cnVzLWVhc3QtMQ&redirect_uri=http%3A//127.0.0.1&state=dontcare&code_challenge_method=S256&scopes=sso%3Aaccount%3Aaccess&code_challenge=wJYlfWmU3ejXGVbUbiXhahQHUaexSrlcLSQahygGCkk

When you get redirected to localhost, paste the OAuth code below:
OAuth code: eyJr...TB2d5pEmNPNJ6GMNypXkAr
Successfully retrieved an AWS SSO access token.
Available AWS accounts:
- Sandbox (12345678901)
- Honeypots (1111111111)

Can an attacker use a malicious redirect URL to steal the authorization token?

One of the most common vulnerabilities in OAuth implementations is allowing attackers to craft malicious authentication URLs that redirect to attacker-controlled domains. When successful, this allows the attacker to intercept the authorization token and impersonate the victim.

In this case, AWS mitigates this risk by restricting redirect URLs to localhost. This restriction ensures that the temporary web server spun up by the AWS CLI is the only recipient of the authorization code. I attempted to bypass this validation using various techniques, including IPv6 addresses with embedded IPv4 components and DNS rebinding. However, all attempts were unsuccessful—which is a good sign and indicates a robust implementation (or that I’m a lousy pentester).

Can this really not be used for phishing purposes?

Absolutely not—at least, not by design, unlike device code authentication, which is inherently susceptible to phishing attacks. The only potential risk would be if a vulnerability exists in AWS’s implementation, for instance if an attacker were able to bypass the redirect URL allow-list. If you’re a skilled application security person, I’d encourage you to give it a try: this would make a great submission to AWS’ new VDP program on HackerOne!

So… are we good?

Unfortunately, although PKCE-based authentication is the right way to go, device code authentication is still available, and there’s no way to disable it. This new release is consequently not closing the very attack vector it’s supposed to mitigate.

AWS, known for their strong commitment to backward compatibility, likely released this early to drive adoption and scale it gradually, and may plan to allow organizations to enforce PKCE-based authentication in the future.

What Should You Do?

  • Upgrade your AWS CLI to a recent version (2.22.0+), which defaults to using the PKCE-based flow (Note: support in aws-vault is coming.)
  • Encourage everyone in your organization to transition to the PKCE-based flow.
  • Use CloudTrail to detect and alert when the old device code flow is being used. You can create alerts by matching CloudTrail logs with the following properties:
    • eventName = CreateToken
    • eventSource = sso.amazonaws.com
    • requestParameters.grantType = urn:ietf:params:oauth:grant-type:device_code
  • If you have access to an EDR or a proxy, you can also block device.sso.<region>.amazonaws.com at the network level. This will ensure that nobody can use device code authentication from a corporate device.
  • If device code phishing is a concern to you, let your AWS TAM know so they can advocate for it internally.

Thanks for reading! You can find me on LinkedIn and Bluesky.

References

https://aws.amazon.com/blogs/developer/aws-cli-adds-pkce-based-authorization-for-sso

https://aws.amazon.com/about-aws/whats-new/2024/11/aws-command-line-interface-pkce-single-sign-on

https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html

https://blog.christophetd.fr/phishing-for-aws-credentials-via-aws-sso-device-code-authentication/

Leave a Reply

Your email address will not be published. Required fields are marked *