This is a simple write up for the first challenge I attempted on CyberDefenders, which can be found here.
Instructions
Use the provided credentials to access AWS cloud trail logs and answer the questions.
Scenario
Welcome, Defender! As an incident responder, we’re granting you access to the AWS account called “Security” as an IAM user. This account contains a copy of the logs during the time period of the incident and has the ability to assume the “Security” role in the target account so you can look around to spot the misconfiguration that allowed for this attack to happen.
Environment
The credentials above give you access to the Security account, which can assume the role of “security” in the Target account. You also have acces to an S3 bucket, named flaws2_logs, in the Security account, that contains the CloudTrail logs recorded during a successful compromise.
Question 1
What is the full AWS CLI command used to configure credentials?
This question can be answered by reading the docs. The answer is aws configure
.
Question 2
What is the ‘creation’ date of the bucket ‘flaws2-logs’?
This question can be answered by logging into the AWS portal with the credentials provided in the challenge, navigating to the S3 Buckets dashboard, and observing the metadata provided for the flaws2-logs
bucket. Note that the creation time displayed is, at least for me, in Eastern Time (UTC -5:00), so some simple arithmetic is needed to get it into the UTC time zone requested by the answer. The answer is 2018-11-19 20:54:31 UTC
.
Environment Setup
The following questions will be answered by analyzing the data in Python. So let’s load some packages, read in the data, and convert all of the logs to a single Pandas dataframe.
import pandas as pd
import os
import json
files = os.listdir(".")
files = [file for file in files if file.endswith(".json")]
data = []
for file in files:
with open(file, "r") as f:
data.append(json.load(f))
for datum in data[1:]:
data[0]["Records"].extend(datum["Records"])
df = pd.DataFrame(data=data[0]["Records"])
df.head()
eventVersion | userIdentity | eventTime | eventSource | eventName | awsRegion | sourceIPAddress | userAgent | requestParameters | responseElements | ... | requestID | eventID | readOnly | resources | eventType | recipientAccountId | sharedEventID | errorCode | errorMessage | managementEvent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1.05 | {'type': 'AWSAccount', 'principalId': '', 'acc... | 2018-11-28T23:09:36Z | s3.amazonaws.com | GetObject | us-east-1 | 104.102.221.250 | [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_... | {'bucketName': 'the-end-962b72bjahfm5b4wcktm8t... | None | ... | EDFBFC9CE11E755F | ea33682d-0829-40c1-9820-bd721b9aede8 | True | [{'type': 'AWS::S3::Object', 'ARN': 'arn:aws:s... | AwsApiCall | 653711331788 | a59b4ac8-6a51-44ff-ab76-e66f75bd95ce | NaN | NaN | NaN |
1 | 1.05 | {'type': 'AWSAccount', 'principalId': '', 'acc... | 2018-11-28T23:09:36Z | s3.amazonaws.com | GetObject | us-east-1 | 104.102.221.250 | [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_... | {'bucketName': 'the-end-962b72bjahfm5b4wcktm8t... | None | ... | 9880010F3D39F3AC | dee6f6a3-f18a-40db-a6fd-b96d05502266 | True | [{'type': 'AWS::S3::Object', 'ARN': 'arn:aws:s... | AwsApiCall | 653711331788 | f8c6cdc8-6ec1-4e14-9a0e-f300b16e282e | NaN | NaN | NaN |
2 | 1.05 | {'type': 'AWSService', 'invokedBy': 'ecs-tasks... | 2018-11-28T22:31:59Z | sts.amazonaws.com | AssumeRole | us-east-1 | ecs-tasks.amazonaws.com | ecs-tasks.amazonaws.com | {'roleSessionName': 'd190d14a-2404-45d6-9113-4... | {'credentials': {'sessionToken': 'FQoGZXIvYXdz... | ... | 6b7d6c60-f35d-11e8-becc-39e7d43d4afe | 6177ca7e-860e-482c-bde9-50c735af58d6 | NaN | [{'ARN': 'arn:aws:iam::653711331788:role/level... | AwsApiCall | 653711331788 | 1d18bf74-8392-4496-9dc4-a45cb799b8b4 | NaN | NaN | NaN |
3 | 1.05 | {'type': 'AWSService', 'invokedBy': 'ecs-tasks... | 2018-11-28T22:31:59Z | sts.amazonaws.com | AssumeRole | us-east-1 | ecs-tasks.amazonaws.com | ecs-tasks.amazonaws.com | {'roleSessionName': 'd190d14a-2404-45d6-9113-4... | {'credentials': {'sessionToken': 'FQoGZXIvYXdz... | ... | 6b80a0b1-f35d-11e8-becc-39e7d43d4afe | 457af3a9-0b1b-44ca-91e1-8f4a0f873149 | NaN | [{'ARN': 'arn:aws:iam::653711331788:role/ecsTa... | AwsApiCall | 653711331788 | 5397e1a9-82c7-4a00-9b1c-e44cbd688aa1 | NaN | NaN | NaN |
4 | 1.04 | {'type': 'AssumedRole', 'principalId': 'AROAIB... | 2018-11-28T23:06:17Z | ecr.amazonaws.com | BatchGetImage | us-east-1 | 104.102.221.250 | aws-cli/1.16.19 Python/2.7.10 Darwin/17.7.0 bo... | {'imageIds': [{'imageTag': 'latest'}], 'reposi... | None | ... | 35ea9256-f362-11e8-86cf-35c48074ab0a | b2867f3e-810c-47d1-9657-edb886e03fe6 | NaN | [{'ARN': 'arn:aws:ecr:us-east-1:653711331788:r... | AwsApiCall | 653711331788 | NaN | NaN | NaN | NaN |
5 rows × 21 columns
Question 3
What is the name of the first generated event according to time?
df.sort_values("eventTime", ascending=True, ignore_index=True).loc[0, "eventName"]
This gives us a value of AssumeRole
, which is our answer.
Question 4
What source IP address generated the event dated 2018-11-28 at 23:03:20 UTC?
df.query("eventTime == '2018-11-28T23:03:20Z'")
eventVersion | userIdentity | eventTime | eventSource | eventName | awsRegion | sourceIPAddress | userAgent | requestParameters | responseElements | ... | requestID | eventID | readOnly | resources | eventType | recipientAccountId | sharedEventID | errorCode | errorMessage | managementEvent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
9 | 1.04 | {'type': 'AssumedRole', 'principalId': 'AROAIB... | 2018-11-28T23:03:20Z | logs.amazonaws.com | CreateLogStream | us-east-1 | 34.234.236.212 | awslambda-worker | None | None | ... | cc9ae337-f361-11e8-894e-cbc2b0778d92 | 483557d2-2b35-4fc6-b682-ff5dbc96eccf | NaN | NaN | AwsApiCall | 653711331788 | NaN | AccessDenied | User: arn:aws:sts::653711331788:assumed-role/l... | NaN |
36 | 1.06 | {'type': 'AWSService', 'invokedBy': 'apigatewa... | 2018-11-28T23:03:20Z | lambda.amazonaws.com | Invoke | us-east-1 | apigateway.amazonaws.com | apigateway.amazonaws.com | {'functionName': 'arn:aws:lambda:us-east-1:653... | None | ... | cc96765b-f361-11e8-a2d8-2b201bd316c5 | 949e83c0-0d98-4b7d-8845-5e2fe3eafde4 | False | [{'accountId': '653711331788', 'type': 'AWS::L... | AwsApiCall | 653711331788 | a63b106b-e331-4778-a6c0-64e397216fde | NaN | NaN | False |
2 rows × 21 columns
Two results are returned, but the answer is obviously 34.234.236.212
.
Question 5
Which IP address does not belong to Amazon AWS infrastructure?
(
df
.groupby(["sourceIPAddress", "userAgent"])
.size()
)
sourceIPAddress userAgent
104.102.221.250 [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36] 22
[aws-cli/1.16.19 Python/2.7.10 Darwin/17.7.0 botocore/1.12.9] 2
aws-cli/1.16.19 Python/2.7.10 Darwin/17.7.0 botocore/1.12.9 3
34.234.236.212 awslambda-worker 5
apigateway.amazonaws.com apigateway.amazonaws.com 2
ecs-tasks.amazonaws.com ecs-tasks.amazonaws.com 2
lambda.amazonaws.com lambda.amazonaws.com 1
dtype: int64
There is only one IP address whose User Agent string does not mention AWS, which is 104.102.221.250
.
Question 6
Which user issued the ‘ListBuckets’ request?
df.query("eventName == 'ListBuckets'")['userIdentity'].values
array([{'type': 'AssumedRole', 'principalId': 'AROAJQMBDNUMIKLZKMF64:d190d14a-2404-45d6-9113-4eda22d7f2c7', 'arn': 'arn:aws:sts::653711331788:assumed-role/level3/d190d14a-2404-45d6-9113-4eda22d7f2c7', 'accountId': '653711331788', 'accessKeyId': 'ASIAZQNB3KHGNXWXBSJS', 'sessionContext': {'attributes': {'mfaAuthenticated': 'false', 'creationDate': '2018-11-28T22:31:59Z'}, 'sessionIssuer': {'type': 'Role', 'principalId': 'AROAJQMBDNUMIKLZKMF64', 'arn': 'arn:aws:iam::653711331788:role/level3', 'accountId': '653711331788', 'userName': 'level3'}}}],
dtype=object)
The last key in the JSON data is userName
, which has a value of level3
, which is our answer.
Question 7
What was the first request issued by the user ‘level1’?
(
df
.loc[df["userIdentity"].astype(str).str.contains("level1")]
.sort_values("eventTime", ascending=True, ignore_index=True)
.loc[0, "eventName"]
)
The answer is CreateLogStream
.