Exploring an AWS account post-compromise

Daniel Grzelak
Jun 23, 2016 · 7 min read

So you’ve pwned an AWS account — congratulations — now what? You’re eager to get to the data theft, amirite? Not so fast grasshopper, have you disrupted logging? Choice! Time to look around and understand what you have.

Your instinct is probably to type “whoami” and luckily AWS has an equivalent.

aws sts get-caller-identity

It won’t give you much but it will start painting the picture. The information returned is “not secret” but it can be painful to obtain otherwise. For example, crafting Amazon Resource Names (ARNs) is a key part doing stuff in AWS but account numbers, a constituent part of ARNs, are not typically disclosed outside an account. The identity ARN will also likely have a descriptive role or user name that may give you immediate feel for your access.

{
"Account": "123456789012",
"UserId": "ABCDEFGHIJKLMNOPQRSTUV",
"Arn": "arn:aws:iam::123456789012:user/root"
}

From here, there are a number of options depending on your goals.

Most big organisations that utilise AWS will connect their data centres and offices directly to Amazon using the Direct Connect service.

AWS Direct Connect links your internal network to an AWS Direct Connect location over a standard 1 gigabit or 10 gigabit Ethernet fiber-optic cable. One end of the cable is connected to your router, the other to an AWS Direct Connect router. With this connection in place, you can create virtual interfaces directly to the AWS cloud …, bypassing Internet service providers in your network path.

Like other AWS services, there is an API to interact with and it can be extremely revealing. Describing locations will display meta data about any connections.

aws directconnect describe-locations | jq '.locations [] | .locationCode + " " + .locationName'

A typical result will include a list of aliases and physical facility locations.

"MyDC1 N 11600 W, Saratoga Springs, UT 84045"
"MyDC2 7135 S Decatur Blvd, Las Vegas, NV 89118"
"NSADC 1400 Defense Pentagon Washington, DC 20301"

That information has a nice synergy (now there is only ‘information super highway’ on my bingo board) with route table data provided by interrogating the EC2 API.

aws ec2 describe-route-tables | \jq '.RouteTables | .[] | .Routes [] | .GatewayId + " " +  .DestinationCidrBlock' | sort | uniq

You should be able to match locations with the returned IP ranges and later use the matches to tunnel back into data centres or sometimes, even corporate networks. The above command filters out the noise, showing only gateway IDs and destination networks.

“local 10.10.10.0/22”
"vgw-12345678 10.0.0.0/8"
"vgw-12345678 192.168.0.0/12"
...

Virtual gateways are associated with virtual private clouds (VPCs), which are the network containers used to group resources like EC2 instances and Lambda functions. If you can compromise such a resource or create one in the right VPC, you should be able to route normally through those gateways unless network ACLs prevent it. Listing NACLs is also an EC2 call.

aws ec2 describe-network-acls | jq '.NetworkAcls [] .Entries [] | .Protocol + " " + .RuleAction + " " + .CidrBlock' | sort | uniq

Any deny rules are valuable in that they provide a ready made target list of things your target does not want outsiders to access. As you might expect, the rules are manageable through API. Protocol numbers are as per the IANA Assigned Internet Protocol Numbers, and -1 is a wildcard for all protocols.

"-1 allow 0.0.0.0/0"
"6 deny 10.1.2.0/24"
"6 deny 192.168.1.2/32"

As an aside. Amazon appears to have made a blunder bundling bazillions (alliteration achievement attained) of extraneous API functions into the EC2 API namespace. It is both common and easy to write lazy AWS policies that allow all actions to be executed in a particular API. For example:

"Action": "ec2:*"

One final query worth running to get a better sense of the network is to ask Route53 for all of the hosted zones.

aws route53 list-hosted-zones | jq '.HostedZones [] .Name'

As you might expect, this produces a simple of domain names owned by your target and controlled in AWS.

"company.com."
"internal-company.com."
"secret-new-product.com."
...

After you’ve mapped all the networky bits, it’s time to move on to identities and access.

AWS Identity and Access Management (IAM) is a web service that you can use to manage users and user permissions under your AWS account.

IAM is where a lot of the magic and good stuff lives. It’s extremely complicated but extremely powerful, and likely where you will be investing a lot of your reconnaissance and persistence efforts.

Listing users will give you a feel for both the size of the account and the scope of attack surface.

aws iam list-users | jq '.Users [] .Arn'

People tend to name users using either their identity or the intended purpose of the account.

"arn:aws:iam::123456789012:user/JohnSmith"
"arn:aws:iam::123456789012:user/Twitter"
"arn:aws:iam::123456789012:user/IntegrationBot"
...

In large enterprise accounts you may find yourself in the odd situation of the user list being tiny. That could be because authentication is federated via a SAML provider. To reveal this situation, list the identity providers using the IAM API.

aws iam list-saml-providers | jq ‘.SAMLProviderList [] .Arn’

Most of the major 3rd party identity services integrate seamlessly with AWS so its not unusual for one of those to be returned.

“arn:aws:iam::123456789012:saml-provider/Octa”
“arn:aws:iam::123456789012:saml-provider/PingIdentity”

The SAML provider an organisation uses for AWS authentication is highly correlated with the provider they use for other cloud services and internal systems — Yay for single sign-on. Knowing this can dramatically change other tactics you employ and reduce rage from banging your head against an invisible 2FA brick wall.

However, the true power of IAM is in roles. Roles are assumed by users authenticating via SAML. Roles are how Amazon recommends you start your EC2 instances and how it forces you to work in newer services like Lambda. Roles are the magic that gives you keyless API calls. Roles are the future.

You can use roles to delegate access to users, applications, or services that don’t normally have access to your AWS resources. For example, you might want to grant users in your AWS account access to resources they don’t usually have, or grant users in one AWS account access to resources in another account. Or you might want to allow a mobile app to use AWS resources… Sometimes you want to give AWS access to users who already have identities defined outside of AWS, such as in your corporate directory. Or, you might want to grant access to your account to third parties so that they can perform an audit on your resources.

If roles are the future, the ‘list-roles’ function is the Almanac from Back to the Future Part II.

aws iam list-roles | jq '.Roles [] .Arn'

In general role names also tend to be quite descriptive. The full list of role ARNs is critical in escalating privileges and moving laterally within AWS.

"arn:aws:iam::123456789012:role/LambdaRole"
"arn:aws:iam::123456789012:role/AdminRole"
"arn:aws:iam::123456789012:role/TestRole"
...

Conveniently, if you are bored and want to go through all user, role, and policy data with a fine-tooth comb (the hyphen is important because I’m not sure what you’d do with a fine tooth-comb?!), there’s an API to retrieve all the details at once.

aws iam get-account-authorization-details

Finally, while SSH key pairs aren’t strictly part of IAM — they are predictably part of EC2 — they are another identifier that can help with mapping how an organisation deals with access.

aws ec2 describe-key-pairs | jq '.[][] .KeyName'

You should see a list of key names, which might tell you who has access to resources and for what purpose. Shared keys might make for good targets.

"janes-ssh-key"
"team-shared-key"
"product-deployment-key"
...

AWS has a pretty extensive and easy to use support program, accessible from the web interface. It turns out that the typical message based support interaction provided by the web interface, is just a thin layer over yet another API.

The AWS Support API reference is intended for programmers who need detailed information about the AWS Support operations and data types. This service enables you to manage your AWS Support cases programmatically.

You can list support cases to gather interesting intelligence about what the account administrators are attempting to resolve and identify areas of concern.

aws support describe-cases --include-resolved-cases | jq '.cases [] | .subject'

… Like a a compromise of their AWS account.

"Limit Increase: EC2 Instances"
"My password doesn't work - please set it to hunter2."
"I think someone hacked our AWS account"
...

At this point, you might consider easing the stress on overworked Amazon support staff by closing any such cases with a comment like, “Nvm dudez, it waz just our Nessus boxen gone rogue”.

Within a single account, support cases are often logged by many users, and those cases can spiral into big CC chains. This provides an opporunity to gather email addresses to target in later activity.

aws support describe-cases --include-resolved-cases | jq '.cases [] | .submittedBy, .ccEmailAddresses []' | sort | uniq

This article was written under the assumption you have access to an AWS API key or role with some reasonably broad permissions, and an up-to-date installed awscli & jq.

More importantly, it was written to enlighten AWS account administrators and improve legitimate penetration testing TTPs. In fact, as I wrote this article, engineers at my workplace implemented mitigations, detection logic and tests for gaps this work identified. Regardless, let’s not fool ourselves, our foes are already Internet Explorers of the Highest Order of Business Excellence. They are setting sail in our clouds right now.

Want to learn to hack AWS? I offer immersive online and in-person training to corporate teams at hackaws.cloud

HackAWS.cloud

100% cyber free

HackAWS.cloud

100% cyber free

Daniel Grzelak

Written by

HackAWS.cloud

100% cyber free