For adversarial scenarios, AWS console access is better than the APIs. We’ll walk you through our research process here, and release a new tool we’ve built!
When there’s a will…
We’re frequently asked by clients to test applications, networks and/or infrastructure hosted on Amazon Web Services (AWS). As a part of these assessments, we’ll oftentimes locate active IAM credentials in a variety of ways. These credentials allow for the holder to perform privileged actions in AWS services within an AWS account.
These credentials come in two forms, both of which work with the AWS CLI:
- Permanent credentials
Usually representing individual IAM users, these contain an access key (which generally starts with
AKIA) and secret key.
- Temporary credentials
Generally come from assuming an IAM role, these also contain a session token (and an access key starting with
ASIA). Generally speaking, these credentials are used by your applications or users with the AWS CLI or SDK to integrate with different AWS services.
However, neither of these are ideal for a penetration test. The AWS CLI often requires multiple calls to obtain relevant data, and the SDK would require developing tools specific for an attack scenario or vulnerability.
Recently, we had a perfect example of this. While performing a cloud penetration test with Karl Fosaaen, we located some hardcoded AWS credentials within a piece of management infrastructure in a client’s account. These credentials worked great with AWS APIs, and were really useful for tools like ScoutSuite and Pacu. Both of these tools utilize the AWS APIs/CLIs/SDKs, which generally have more power than the console.
However, it’s easy to forget specific CLI syntax for even the simplest of commands. The below example shows a pretty simple recon activity we might perform – describing running EC2 instances. At the top, we see the AWS console interface’s easy-to-use filter option. On the bottom, we see my attempts to remember the specific filter name needed.
Rather than trudge through the AWS CLI syntax, we investigated methods to gain access to the AWS console.
…there’s a way.
Fortunately, AWS has such a mechanism for gaining access to the AWS console using API credentials – the AWS federation endpoint.
On the Topic of Federation
AWS has written a handful of blogs (1, 2) on the topic, and has a short instruction guide on its use. The long and the short is that it allows for the conversion of certain types of temporary credentials to console federation tokens. These tokens can be included in to a URL which grants access to the AWS console. All that’s needed is a set of calls to the federation endpoint, and you’re given a magic token that allows direct access to the AWS console.
There’s a couple of caveats, though – you need temporary credentials. The most common mechanism to obtain these credentials is with a call to the
AssumeRole method in the STS service – as the name suggests, this allows a caller to gain access to an IAM role with an appropriate role trust policy.
sts:AssumeRole is generally regarded as a “dangerous” permission, in that it can be used to significantly elevate privilege – for that reason, it’s a permission we recommend clients avoid granting unless necessary. Regardless, we’d also need to know of a role we can assume, which may or may not be available. There are other common ways to obtain credentials – the most common being
sts:GetSessionToken, which is often used to grant temporary credentials for a user based on a multi-factor authentication mechanism – however, credentials issued from this endpoint do not work with the federation endpoint, and will return an error when attempting to use them.
Lucky for us, there’s another available option. While generally designed for IDP authentication from outside an AWS account,
sts:GetFederationToken fits our needs quite nicely. This appears to be an older function of the AWS APIs, before newer authentication features were available (such as AWS SSO, AWS Cognito, and Web Identity/SAML federation). The permission is designed to be authenticated via a set of permanent IAM credentials (i.e. an on-premises application server) and federate a non-IAM user into AWS by providing temporary access credentials. The method allows for specifying the permissions of the new user via one or more policies – permissions are taken as the intersection of the calling IAM user and the policies supplied in the
In our case, we’re not concerned about reducing our access, so we can supply the built-in AWS-managed
AdministratorAccess policy for the fullest set of effective permissions. To make our lives easier, this API method is considered non-dangerous, and is included in the AWS-managed
ReadOnlyAccess policy. This is in-line with our own uses here – we’re not escalating access, just making it easier to use. Still, console access can be used to hunt for further privilege escalation opportunities.
With these temporary credentials in hand, we can call the federation service for our magic console sign-in link. In the response, we get a URL for obtaining access to the AWS console. No more copy/pasting from a window!
The right tool for the right job – AWS Consoler
To make the instrumentation process a bit easier, I’ve developed a tool for gaining access to the AWS console, which I’ve named “AWS Consoler” (creative, I know…). Check the tool out (and full usage instructions) in the GitHub repo. As with all of our open-source tools, we welcome pull requests!
This tool has a variety of features, and deep integrations with AWS’s SDK for Python (boto3). Credentials can be passed on the command line, as one might expect. However, because we’re powering this with boto3, they can also be taken from AWS CLI named profiles, via boto3’s built-in logic for environment variables, or even the IAM Metadata Service (when running on AWS compute resources) – boto3 does all the heavy lifting for us. Pass in your credentials, and you’ll get a sign-in link for the AWS console in the region of your choice. If you hate copy/pasting as much as I do, it can also open that link in your system’s default browser.
Here’s some example usage we would expect would work for most folks.
$> aws_consoler -v -a AKIA[REDACTED] -s [REDACTED] 2020-03-13 19:44:57,800 [aws_consoler.cli] INFO: Validating arguments... 2020-03-13 19:44:57,801 [aws_consoler.cli] INFO: Calling logic. 2020-03-13 19:44:57,820 [aws_consoler.logic] INFO: Boto3 session established. 2020-03-13 19:44:58,193 [aws_consoler.logic] WARNING: Creds still permanent, creating federated session. 2020-03-13 19:44:58,698 [aws_consoler.logic] INFO: New federated session established. 2020-03-13 19:44:59,153 [aws_consoler.logic] INFO: Session valid, attempting to federate as arn:aws:sts::123456789012:federated-user/aws_consoler. 2020-03-13 19:44:59,668 [aws_consoler.logic] INFO: URL generated! https://signin.aws.amazon.com/federation?Action=login&Issuer=consoler.local&Destination=https%3A%2F%2Fconsole.aws.amazon.com%2Fconsole%2Fhome%3Fregion%3Dus-east-1&SigninToken=[REDACTED]
If you’re a developer or engineer working on an AWS environment, you might be wondering “how do I prevent this?” Fortunately, this type of access is relatively-easy to prevent, and falls in to our methodology for securing cloud environments:
Scope down permissions to the lowest-possible set
When setting up IAM permissions for a user, role, or other IAM principal, only grant the permissions which are absolutely necessary for normal operation. Additional permissions like
sts:AssumeRole, and other STS operations pose a risk towards privilege escalation, and can be dangerous if applied incorrectly.
If you’re using a policy managed by AWS, we’d recommend verifying the policy in-use doesn’t include permissions that are not needed for normal operation. Replacing AWS-managed policies with customer-managed policies is one of our suggestions for scenarios where the AWS-managed policy doesn’t exactly match the specific requirements for a IAM user or role’s purpose.
Don’t hard-code credentials in your environment
The initial entry point for this escalation requires obtaining AWS credentials. Often times, we’ll find these hard-coded into an environment in some fashion. We’d recommend transitioning to using things like execution roles for your compute resources in AWS. Implementations vary per compute resource – for example, EC2 has instance profiles, ECS has task roles, and Lambda has execution roles. By doing so, AWS SDKs can automatically obtain short-lived credentials for authenticating to AWS services.
Restrict access to the EC2 Instance Metadata Service
The EC2 Instance Metadata Service is a web service running in all EC2 environments at a pre-defined IP address. Normally, this service is used by compute resources to gain information about the environment which they were deployed to. This information usually include things like the following:
- VPC configuration
- EC2 userdata
- Tags associated with the EC2 instance
- Temporary IAM credentials for execution roles
Ensure you’ve restricted access to this service from resources in your application environment, especially if processing user data on said resources. In general, you’d restrict this using iptables rules, Windows Firewall rules or other routing rules on your compute resources. In addition, you can restrict this can be set at instance launch time.
Note that, if using ECS, an additional metadata endpoint exists for task-specific metadata – the ECS Task Metadata Service. Make sure you restrict access to this as well.
Set condition keys on IAM policies for execution roles
When using execution roles with your compute resources, consider restricting the policy to be only usable by that specific resource. This can be accomplished by a number of condition keys within the IAM policy, including ones restricting the VPC and/or IP address of the caller.
With this blog, and AWS Consoler, we hope to make the lives of red-teamers, blue-teamers, bug bounty searchers, and other individuals in the security sphere easier. Feel free to let us know how you’re using AWS Consoler via the comments below, or on your favorite social networks. You can find me on both LinkedIn and Twitter.