In part three of F-Secure Consulting's Attack Detection Fundamentals workshop series for 2021, we covered an end-to-end kill chain, from initial access and discovery using some 'compromised' credentials, through to the installation of persistence and the exfiltration of data from an S3 bucket.
In the previous lab, we performed some basic enumeration with our compromised user credentials. We gained an understanding of the policy applied to our user, the likely misconfigured privileges it had to modify IAM and the buckets it was provisioned to list and retrieve objects from.
While we'll leave the S3 buckets until our final lab, we've seen enough to know we have the necessary privileges to install some persistence within the target account. In this lab we'll look to add an additional access key to our compromised user, and add a login profile so we can browse the target account through the web UI.
NOTE: The corresponding CloudTrail log can take fifteen minutes or more to arrive following an API call being made, so expect some delay following your activities!
DISCLAMER: Set up of the tools and the testing environment is not covered comprehensively within this lab. We will assume basic familiarity with command line and the ability of the reader to build the necessary tools.
For this lab we'll make use of the Pacu framework. Having installed it, we can launch it and configure a new session.
Running a "set_keys" command, we can input the details of our compromised access key, providing an alias so we can keep track of the entity we're operating as.
With our identity configured, we can run an "ls" command to view the modules that Pacu has available. For our purposes, we'll take a look at the "iam__backdoor_users_keys" module. We can prepend the module name with "help" to see more information about its use.
We can see that this module will add a new AWS API key for the users in the account that we specify. Given the IAM full control that we have, we could use this module to install a new API key to every user in the account! For now though, we'll simple add an additional key to our original compromised user.
We can see the installation of this key in the IAM console. Browsing to the below location, we can see all access keys created for the "customer_data_management_user" (note the second key present!).
IAM > Users > customer_data_management_user > Security Credentials
OK, so we have an additional API key with which to use our compromised account; but let's take this a bit further. Take note of that "Sign-in credentials" section of the screenshot above; the user has no console password set. Looking at the information for the "iam__backdoor_users_password" module, we see that we can add a password to accounts too, allowing us to access them through the web UI!
Again, we'll just apply this to our originally-compromised user, but with the privileges we hold, we could do the same to any user in the account.
Running the module against our "customer_data_management_user" identity, we are returned a randomly-generated password.
Viewing the users details in the console, we can see the "Console password" field has changed.
Taking the account ID we can get from a GetCallerIdentity API call (as we did in lab one), we can browse to the AWS login portal and attempt to login.
Well, there we go - we now have the ability to explore the account through the UI! With this access, we can explore the IAM configuration of the account, add users, modify our own permissions to escalate to a fully-fledged admin, whatever you like!
It's worth noting what's happening (and not happening) here. We haven't actually really modified the permissions of our user here. While we can browse around, the same inline policy we saw in the first lab is still in place. If you attempt to view any deployed EC2s for example, you'll get lots of nice "AccessDenied" errors that will also be logged in CloudTrail. Further, the privilege escalations we mentioned above are already possible from the CLI, we've just added the ability to access the account free of the access keys that gave us the initial access.
So, unlike last time, we've made some significant changes to our target account. Rather than just retrieving information with 'read only' API calls, we've added a new API key, and generated a new console password to login through the browser.
Let's return to Athena and once again see what API calls have been made by our "customer_data_management_user" identity. This time, we'll add a filter to ensure 'read only' events aren't returned.
WHERE userIdentity.username = 'customer_data_management_user'
AND readOnly = 'false'
There's a few things we can observe from the returned log entries below (omitting the additionaleventdata field for the purposes of the screenshot).
Firstly, the "CreateAccessKey" and "CreateLoginProfile" API calls themselves. Filtering the above columns, we can see our "customer_data_management_user" user adding a new key to itself, and creating a login profile that doesn't require a password change upon first login (based on the "requestparameters" field).
Next, as we spoke about in the last lab, our "useragent" column shows us the two API calls have been made with "boto3" (the python library behind Pacu and many other python-based tools that interact with AWS).
Things get a bit more exciting when we consider our third API event, the "ConsoleLogin". Aside from the obvious implications of that event in isolation, we now have our compromised user, which usually operates programmatically, making a web login with a browser user agent. Gulp!
Looking at the data within our "additionaleventdata" column below, we can also see greater cause for concern, the login was completed without MFA (as we might expect with our compromised user logging into the console for the first time).
As you might expect, the web-based AWS management console makes use of the same APIs we've been interacting with from the command line. If we add our read-only events back into our Athena SQL query and look at the events that occured following our "ConsoleLogin" event, we see the following:
WHERE userIdentity.username = 'customer_data_management_user'
Note above how the user agent changes to "console.amazonaws.com" following the console login, and the API calls generated by exploring IAM resources have this user agent instead (with some exceptions). All very suspect for our compromised user!
While not something we'll explore here, we could do more to enrich the data from CloudTrail. As an example, we could do a geo-ip lookup to see the approximate source location of the login (and the preceeding API calls). If we're expecting the above, high-value, configuration changes to take place from known locations, what we've seen from these CloudTrail events could be worthy of investigation.
In this lab, we've started affecting change on our target account, adding a second access key, adding a login profile, before finally logging into the account.
We filtered our CloudTrail logs in Athena to omit the read-only events, and saw the "CreateAccessKey" and "CreateLoginProfile" API calls, followed by the "ConsoleLogin" event that included metadata showing a lack of MFA in use. We saw how the user agents changed as we went from command-line to web console access, and considered the potential for geo-ip enriching our logs to make more informed decisions on what is and isn't suspicious.
In the final lab, we'll turn our attention to the customer data stored in an S3 bucket within the account; first listing its contents, before exfiltrating the customer records within.