Attackers are expanding access through Amazon Cognito


Amazon Cognito (aka AWS Cognito) provides identity and access management (IAM) for AWS web applications. Improperly configured Cognito web portals can allow attackers to go off the rails and gain direct access to your AWS control plane.

The Expel security operations center (SOC) recently noticed attackers exploiting a misconfigured Amazon Cognito configuration, allowing them unauthorized access to our customers’ AWS environments. This allowed the attackers to access the AWS control plane and run arbitrary API calls using their Amazon Cognito access.

If you find Amazon Cognito hard to understand, you’re not alone. For us non-engineers, Amazon Cognito is an IAM service that does a few important things. It:

  • integrates with web portals to allow logins and user sign-ups;
  • federates web portal logins with security assertion markup language (SAML) and OpenID Connect, and with common identity providers like Facebook, Google, Apple, or even AWS itself; and
  • allows logged-in or unauthenticated user access to AWS resources through a locked-down, on-rails experience through the Cognito integrated web app.

Amazon Cognito can be a secure way for users to sign up and authenticate to web apps, but insecure configurations can allow attackers to take advantage of Cognito to access your AWS environment in unintended ways. We’re dubbing this attack tactic “AWS Cognition” since “Amazon Cognito weak configuration exploitation” is kind of a mouthful.

To explain how attackers can directly access the AWS control plane, we need to explain how Cognito lets users interact with AWS resources.

I’ll try to keep things simple, but there are three Cognito concepts that we need to understand:

  1. User pools
  2. Identity pools
  3. Access tokens

User pools

User pools are OpenID Connect Identity providers that are configured for your Cognito integrated login system. These include identity providers like Facebook, Google, and Apple, who federate with your web app and handle Cognito authentication.

Think of user pools as the authentication portion of Cognito. They manage user profiles and verify user identities for your web app.

Identity pools

AWS defines an identity pool as “a directory of federated identities that you can exchange for AWS credentials.” They’re what give users authenticated through Cognito access to AWS resources. Typically this access is going to be strictly limited by the policies placed on the user. Depending on how you set up Cognito, you can allow users in identity pools to directly access an AWS access key.

Identity pools are essentially the authorization portion of Cognito. They provide temporary AWS credentials for Cognito authenticated users so they can access AWS resources.

You aren’t required to configure identity pools for your users, and without being part of an identity pool, users won’t generate AWS credentials upon login through Cognito.

Access tokens

These are JSON web tokens which are returned to users after they login through Amazon Cognito. The other tokens returned are the ID and the refresh token for the authenticated user. The access token represents a temporary AWS credential (an ASIA temporary access key) which is created when the Cognito credential provider creates a new assumed role session for a user login session.

For most web apps, a user’s AWS access will be exercised through the app’s GUI using the access token as the credential. This separates the Cognito user from the AWS control plane by essentially putting the experience on rails. As long as users have the web app to interact with AWS resources, they’re in a fairly safe box where they can’t interact with anything they aren’t supposed to. But there’s a feature in Cognito that web app developers can overlook: access tokens can be exchanged for access keys if the attacker knows the identity pool ID related to their account.

So, trading an access token for an access key—why does this matter? Sounds like the same level of access, right? In fact, I said earlier that the access token represents the access key. Why worry? I also mentioned that the access token keeps the user on-rails with the access token they can access AWS through the web app. With the access key the user can directly access the Cognito-assumed role through the AWS command line interface (CLI)—or through other means. Once users access the AWS CLI, they’re off the rails set up in your web app and it’s up to your IAM policies to restrict access.

How it happens

When misconfigured, an attacker can acquire the Cognito identity pool ID, user pool ID, user pool client ID, and region from your web portal. These values are often hardcoded in the application’s source code, in JavaScript files, or included in HTTP responses. After acquiring the identity pool ID, attackers can use it along with their access token to perform a “token exchange request,” which exchanges their JSON web token for a temporary AWS credential.

To find these identity pool IDs, attackers can search the JavaScript source code on web servers for fields like “identity_pool” since JavaScript runs locally on the web app client’s browser. If your JavaScript function exposes the identity pool ID, attackers will be able to see that in their browser’s development tools.

The identity pool ID could also be exposed in HTTP responses from web apps using Cognito.

Pankaj Mouriya wrote an excellent article for the Appsecco blog detailing how this attack looks from the attacker’s point of view. TL:DR; attackers can leverage simple Boto scripts to exchange their access tokens for temporary AWS credentials. If you’re looking for more info on this attack, I highly suggest checking out his article.

What we saw

Expel has seen Amazon Cognito attacks a few times over the years, with varying degrees of attacker success. We’ll focus on the most recent attack our SOC triaged.

The Expel SOC received an alert for Possible enumeration of Lightsail instances, a custom detection we surfaced from AWS CloudTrail logs ingested from our MDR customers.

The AWS user was a Cognito authenticated user. This was hinted at by the AWS role name “CognitoIdentityCredentials” and confirmed by the federation details also found in the CloudTrail log:

“federatedProvider”: “cognito-identity.amazonaws.com”

From our experience, Cognito roles typically have a naming convention that indicates something about the web app they’re associated with and that it’s a Cognito authenticated user. Role names like arn:aws:sts::555555555555:assumed-role/Web_app_user/CognitoIdentityCredentials aren’t uncommon, but this appears to be a best practice rather than a rule.

The Cognito user had run hundreds of API calls programmatically in the moments before the alert. The majority returned the error “AccessDenied.” This is a high-fidelity indicator that something is off, and can be indicative of attackers attempting to discover how far their newly obtained privileges extend.

We analyzed the AWS services interacted with by the Cognito user. We saw that the user ran API calls associated with 50 different AWS services. This is another high-value indicator—most AWS roles only interact with a few services.

We confirmed that the Cognito role was performing malicious actions. Now we had to figure out how the attacker got access to the role. Normally, the user would have limited access to the Cognito role. That access would be through the web app GUI, which wouldn’t allow arbitrary API calls to be run. We also saw that the user agent wasn’t normal for the role. The attacker accessed the CognitoIdentityCredentials role using the AWS CLI, which indicates direct access to the AWS control plane, rather than access through the web app.

Given this information, and the results of our investigation, we determined that sure enough, the attack vector was Amazon Cognito.

We worked with our customer to determine that its web application was at fault, and was exposing the user’s identity pool ID along with the access token. Luckily, their IAM policies were strong and the attacker didn’t have permissions to run any API calls that would harm their AWS environment.

What to look for

How can you identify Amazon Cognito in your environment? What can you look for if you suspect abuse?

When users login through Amazon Cognito, many users are given the same assumed role, which is typically be named something like arn:aws:sts::555555555555:assumed-role/Web_app_user/CognitoIdentityCredentials. The best way to track a particular user’s activity with that role is through the AWS access key ID, which will be a unique temporary key for each user. The key ID will be an alpha-numeric string beginning with ASIA, like “ASIAXXXXXXXXXXXXXXXX.”

Keep an eye out for Cognito users calling APIs associated with enumerating permissions, or cloud discovery. Upon gaining access to the environment, attackers will likely enumerate permissions to identify potential attack paths allowing them to escalate privileges and establish persistence. If you’re interested in what API calls might indicate discovery you can download Expel’s AWS mind map, which maps API calls to MITRE ATT&CK tactics.

Bursts of API calls associated with many different AWS services are another sign that attackers are testing out their newly obtained access.

User agents can also give insight into attacker activity. Many times attackers will access the environment through the AWS CLI or Boto clients. These show up in the “User_Agent” field in CloudTrail logs. Cognito roles using an AWS CLI user agent should be a huge red flag, as they represent command line access to the AWS control plane. Boto clients can represent programmatic access to AWS, which, depending on how your Cognito roles work, could be a sign that a user obtained direct access to the AWS control plane.

Lastly, if you know what API calls your Cognito-enabled role is supposed to perform, look for any that the role shouldn’t be running. That should be a clear sign that an attacker is abusing their access.

Things you can do to keep your org safe

If your org uses Amazon Cognito, the number one thing you can do to keep it secure is to make sure your IAM policies are strict for Cognito roles, so that even if access is granted to attackers, they’ll have no more access than through the web app. You’ll also want to audit your web apps to make sure that they don’t provide Cognito users with their identity pool ID, either through JavaScript functions or through HTTP responses.

If this topic piques your interest, keep an eye out for our annual threat report, which we’ll be publishing in the coming weeks. In the meantime, feel free to drop us a line with any questions.