visit
By Maya Levine, Technical Marketing Engineer, and Lior Sonntag, Security Analyst
A sign of a truly sophisticated attack in the cloud is the ability to move laterally undetected. Doing so successfully requires knowledge of many techniques. In this latest installation of the : Attack and Investigation Series, we present the most involved attack flow yet. We will break down all of the steps a threat actor took to successfully exfiltrate data out of an AWS account.This attack began with a compromised pair of AWS access keys. The first thing the threat actor does is obtain temporary credentials using the
get-session-token
command. This is a form of defense evasion – if the permanent credentials the threat actor started with are deleted or disabled, they will still have access to the environment.In order to understand what the permissions of this user will allow them to do, they run the
list-user-policies
command. Turns out, they do not have permissions to run that command. This is true for the two other commands they attempt: list-attached-user-policies
and list groups for users
which shows them managed policies for the user.At this point, they could resort to a brute force attack to obtain the permissions. This will require a lot of time and effort. So instead, they check for access to the account’s event history. Using the credentials they already obtained, they run the
lookup-events
command to check for access to CloudTrail logs. The output shows them that the user has read-only permissions to CloudTrail logs.This will give the threat actor the ability to understand what they can accomplish with this compromised user. They begin by searching CloudTrail events for that username. The results show that the user ran the
AssumeRole
command to the LambdaCreator
role. They also learn the default region of the user, the role session name, and most importantly – the role ARN. They copy the ARN to later assume the role.The threat actor will check what events the
LambdaCreator
role did in that session. Once they assume the role and move laterally, they will be able to execute those same commands. The first event was UpdateFunctionConfiguration
on a function called Automation-UpdateSSMParam
. The second event was UpdateFunctionCode
on that same function.Finishing up the reconnaissance from the CloudTrail logs, they will now run the
sts assume role
command to escalate their privileges to this LambdaCreator
role.Once in the new role – they try the
get-function
command but get an Access Denied error. Meaning, they cannot access the function’s code or environmental variables. So, they will turn to a form of social engineering: googling the function name. They find an AWS walkthrough which tells them that the role attached to this lambda function has permissions for AmazonSSMFullAccess
. The name implies that if the threat actor is able to gain access to this role, they can easily abuse it, move laterally, and escalate their privileges. Additionally, the actor learns that the function uses boto3 library.The walkthrough also tells them that the AWS name for this function (
Automation-UpdateSSMParam
) is identical to the name in the victim’s account. This is a common mistake users make—defining resource names exactly as AWS defines them in their examples. It makes the jobs of threat actors infinitely easier. A simple Google search gave them all of the information they needed to continue in the attack.Remember there were two permissions the LambdaCreator role used in the CloudTrail logs. Abusing
UpdateFunctionCode
involves changing the function’s code to be malicious and retrieving environmental variables through a reverse shell. This could break the functionality of the function and alert the victim of an attacker’s presence. So, the better alternative is using the UpdateFunctionConfiguration
permission to inject a malicious lambda layer to the function. This does not affect the functionality of the function whatsoever.The code of Lambda layers is stored separately from the rest of the function. These layers can be shared cross-account. First, the actor creates the malicious layer in their own AWS account. They insert malicious code into a boto3 library. When the lambda function is invoked, it will use this altered boto3 library instead of the default one. This malicious code will result in the lambda sending all it’s environments variables to the attackers IP, including the STS token of the function’s role.The actor makes this malicious layer publically available. Then, switching to the victim’s account, they run the
UpdateFunctionConfiguration
command to insert the malicious backdoor layer.Once again, the threat actor can impersonate this, move laterally, and escalate their privileges. In this new role, they have SSMFullAccess permission. They will now repeat their previous technique of looking at CloudTrail to list the history events – this time related to the SSM service.
One of the events is
SSM start session
API call. This will start a connection to an EC2 instance. The actor copies the instance target ID. Using the STS token from before, they start a session to that target ID and login to that EC2 instance.From there, they search for metadata service which by default is found under the IP 169.254.169.254. They now extract the EC2 instance’s role name: DynamoDBFullAccess. Users often name roles based on their permissions, which indicates to threat actors the scope of that role. The name clearly states that the role has full access to DynamoDB.
The role name is added to the query, and the metadata service will output it’s STS token.The first alert is Permissions scan attempt executed by CLI.
This will show all the commands executed by the attacker from the CLI…
ListUserPolicies
, ListGroupsForUser
, ListAttachedUserPolicies
, GetSessionToken
, and AssumeRole
. The AssumeRole
is indicating when the threat actor moved from the original LowPriv role to the LambdaCreator role.The
GetSessionToken
command also prompts this alert: Temporary Creds created from permanent user creds.The next logical step in an investigation is to take all the tokens from this event and create a new query that searches for CloudTrail logs with these tokens.Note that the
AssumeRole
command’s response has another token that was added to this query.This reveals more actions taken by the attacker once they moved laterally to a new role. Specifically, the
UpdateFunctionConfiguration
call which was run on the Automation-UpdateSSMParam function.Back in the alerts list, we see that there are 2 alerts related to the Automation-UpdateSSMParam function. The first is Lambda configuration update.
The second is Lambda Layer was added from an external account.
The logs show that the
UpdateFunctionConfiguration
event has no known identity. They also show that a backdoor layer was inserted from a completely different AWS account.From this same view we also see an
AssumeRole
command…the attacker escalated their privileges to the LambdaSSM role. As part of the investigative process of “following the token”, we copy the access key ID of the role and insert it into the same query that has been built out slowly.There were several alerts generated for the LambdaSSM role as well…the first is Abuse of access token generated by STS dedicated for lambda.
The logs show a
StartSession
call from an external source with a Kali Linux user agent, therefore, this was not initiated from the lambda function itself. Most importantly, the StartSession
call requests an ID.Searching for this ID reveals two alerts. First, is an alert called Suspicious NTP packets volume per session. This shows the malformed NTP traffic that was the attacker exfiltrating the data.
The other alert is Anomaly Detection – Anomalous Network Traffic. As the name suggests, it utilizes machine learning to do anomaly detection of network traffic.
The Statistics tab shows a pretty active traffic trend with ups and downs. However, the
StartSession
action caused a significant enough uptick to be deemed an anomaly.Altogether, all of these events indicate an incident. Throughout the course of the investigation, we followed the token. This allowed us to see the entire attack from beginning to end. From the low-priv-user, the move to automation-updateSsmParam, to
StartSession
with the SSM role.