AWS re/Start Lab · Seguridad · CloudTrail

Working with AWS CloudTrail

A defacement on the Café web server triggers a full incident response cycle: enable CloudTrail, analyze logs with grep, the AWS CLI and Amazon Athena, identify the IAM user behind the attack, and remediate both the AWS account and the EC2 instance.

Lab Summary

Investigated and remediated a real defacement on the Café Web Server. CloudTrail was enabled before the attack happened, so the audit trail captured the unauthorized API call. After escalating from grep on raw JSON logs to AWS CLI lookup-events to Athena SQL queries, the attacker (IAM user chaos) and the rogue OS account (chaos-user) were identified, removed, and the security group was restored to a safe baseline.

Detection

Enabled a CloudTrail trail named monitor storing logs in S3 (KMS-encrypted). Within a minute the Café website was defaced — and every API call leading to it was logged.

Triage at three levels

Used grep on the raw JSON, then AWS CLI filters by ResourceType=AWS::EC2::SecurityGroup, and finally an Athena SQL query that surfaced the attacker in seconds.

Remediation

Killed the active chaos-user SSH session, deleted the OS account, removed the rogue inbound rule SSH/22 0.0.0.0/0, disabled SSH password authentication, and deleted the IAM user chaos.

Recovery

Restored the original Coffee-and-Pastries.jpg from the backup file the attacker left behind in /var/www/html/cafe/images/.

Incident Snapshot

Sofía's investigation kicks off after Martha and Frank notice the homepage was modified. Initial evidence before any analysis:

Initial Findings

Asset
EC2 instance Café Web Server (WebSecurityGroup)
Symptom
Bakery photo replaced by a cartoon monkey wearing "Be Cool" sunglasses
SG state
Unauthorized inbound rule: SSH/22 0.0.0.0/0
Open questions
Who did it? When? From which IP? Console or API?

Investigative Plan

  • Enable CloudTrail before looking at any other evidence — capture future activity.
  • Download the CloudTrail logs from S3 onto the web server itself and inspect them with grep.
  • Narrow the search with aws cloudtrail lookup-events filtered by resource type.
  • Move to Athena once filtering with the CLI becomes tedious.
  • Contain, eradicate, recover — in that order.

Incident Timeline

Documented chronologically by phase of the incident, not by AWS service. Each step shows the actual commands executed and the evidence captured.

01

Pre-incident hardening — SSH restricted to "My IP"

  • Located the Café Web Server instance and opened its security group.
  • The SG started with a single inbound rule: HTTP/80 from anywhere — expected for a public website.
  • Added an inbound rule for SSH/22 with source My IP, so the CIDR was a /32, not 0.0.0.0/0. This becomes important later: anything wider than /32 on port 22 is a red flag.
  • Browsed http://<WebServerIP>/cafe/ to confirm the site rendered normally. Baseline established.
Why this matters: Confirming the baseline before enabling CloudTrail means anything detected afterwards is signal, not noise from the initial setup.
02

Enable CloudTrail and observe the defacement

  • From the CloudTrail console, created a trail with the exact name monitor (the lab depends on this name).
  • Storage: new S3 bucket monitoring#### (four random digits, in this run monitoring0423).
  • Encryption: a new KMS alias of the form <initials>-KMS.
  • Log events: kept the default (management events), then created the trail.
  • Returned to the Café browser tab, did a hard refresh (Shift + reload). After about a minute the bakery cake photo was replaced by a monkey illustration. The site had been defaced.
  • Back in the EC2 console → Security tab → the SG now had a second inbound rule: SSH/22 from 0.0.0.0/0. Not added by the operator.
  • The attack chain was now visible at two layers: a static asset under /var/www was swapped, and the SG was modified through the AWS control plane.
Gotcha: CloudTrail delivers events to S3 in roughly 5-minute batches. The attack happens fast; the evidence shows up later. Don't panic if the bucket is empty for a few minutes after enabling the trail.
03

First-pass analysis with grep and the AWS CLI

  • SSH'd into the Café Web Server as ec2-user (using the /32 rule added earlier — note the irony of investigating from inside the compromised instance).
  • Created a working directory and downloaded the entire trail bucket recursively:
mkdir ctraillogs && cd ctraillogs
aws s3 ls
aws s3 cp s3://monitoring0423/ . --recursive
# Logs land in AWSLogs/<account-num>/CloudTrail/<Region>/<yyyy>/<mm>/<dd>/
cd AWSLogs/.../...
gunzip *.gz
  • Inspected one entry to learn the schema (each event is a single JSON document):
cat <filename>.json | python -m json.tool

Standard fields surfaced: awsRegion, eventName, eventSource, eventTime, requestParameters, sourceIPAddress, userIdentity.

  • Filtered all events by sourceIPAddress and eventName across every file with a bash for loop:
for i in $(ls); do echo $i && cat $i | python -m json.tool | grep sourceIPAddress; done
for i in $(ls); do echo $i && cat $i | python -m json.tool | grep eventName; done

The output was noisy — a flood of Describe*, List* and the occasional Update* action. Useful to confirm the trail was working, but not enough to pinpoint the attacker. Time to switch tools.

  • Moved to the AWS CLI's CloudTrail lookup, filtered by resource type and then by the specific SG ID:
# Discover region from the EC2 instance metadata service
region=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document \
  | grep region | cut -d '"' -f4)

# Resolve the SG ID of the Café Web Server by tag
sgId=$(aws ec2 describe-instances \
  --filters "Name=tag:Name,Values='Cafe Web Server'" \
  --query 'Reservations[*].Instances[*].SecurityGroups[*].[GroupId]' \
  --region $region --output text)
echo $sgId

# Console logins on the account (returned essentially just the current user)
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=ConsoleLogin

# All actions on any SG, then narrowed to the Café SG
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=ResourceType,AttributeValue=AWS::EC2::SecurityGroup \
  --region $region --output text | grep $sgId
Observation: The ConsoleLogin filter returned almost nothing interesting. That's already a clue — if there's no rogue console login, the attacker likely used programmatic access (an API key, not a browser).

The CLI lookups returned useful but still hard-to-read text blocks. For a wider search across fields like useridentity.userName, SQL on Athena is the better tool.

04

Breakthrough — Athena SQL over the CloudTrail logs

  • From the CloudTrail console → Event history → Create Athena table, pointing at the same monitoring0423 bucket. Athena generated a CREATE EXTERNAL TABLE that maps each JSON field to a column. The useridentity column is a struct, and resources is an array.
  • In the Athena Query Editor, set the query results location to s3://monitoring0423/results/ under Settings → Manage.
  • Started with a small exploratory query:
SELECT useridentity.userName, eventtime, eventsource, eventname, requestparameters
FROM cloudtrail_logs_monitoring0423
LIMIT 30;

Useful for understanding the column layout. Not enough to find the culprit. Followed the challenge hints and built up the filter step by step until landing on the query that solved the case:

SELECT DISTINCT useridentity.userName, eventName, eventSource
FROM cloudtrail_logs_monitoring0423
WHERE from_iso8601_timestamp(eventtime) > date_add('day', -1, now())
ORDER BY eventSource;

Two complementary filters made the answer obvious. A tighter version using WHERE eventsource = 'ec2.amazonaws.com' AND eventname LIKE '%Security%' isolates only SG-related EC2 events, and the unauthorized AuthorizeSecurityGroupIngress stands out clearly with a userName that is not the current operator.

Verdict — who did it

  • IAM user: chaos
  • API action: ec2:AuthorizeSecurityGroupIngress against the Café Web Server SG
  • Access method: programmatic (no console login event matched the same user)
  • Time & source IP: captured in the row's eventtime and sourceIPAddress columns inside the Athena result set
05

Containment, eradication and recovery

Identification was only step one. The remediation is the part that actually stops the bleeding — and it has to happen across three different layers: the OS, the IAM control plane, and the static content of the website.

5.1 — Evict the OS attacker

  • sudo aureport --auth confirmed an unknown account chaos-user had recent auth records.
  • who showed chaos-user was still logged in over SSH.
  • First sudo userdel -r chaos-user failed — the user was active — but the error returned the live PID.
  • sudo kill -9 <PID> dropped the session, then sudo userdel -r chaos-user succeeded.
  • sudo cat /etc/passwd | grep -v nologin confirmed only standard Amazon Linux accounts (root, sync, shutdown, halt, ec2-user) remained.

5.2 — Harden SSH

  • sudo ls -l /etc/ssh/sshd_config showed the file had been modified today — also part of the attack.
  • Edited /etc/ssh/sshd_config in vi:
# Before
PasswordAuthentication yes
#PasswordAuthentication no

# After
#PasswordAuthentication yes
PasswordAuthentication no
  • Restarted the daemon with sudo service sshd restart. From now on, only key-pair authentication is accepted — a guessed/leaked password is no longer enough.

5.3 — Close the network door

Removed the rogue inbound rule SSH/22 0.0.0.0/0 from the Café Web Server SG. Only the original HTTP/80 and the SSH/22 from /32 remained:

5.4 — Restore the website

The attacker conveniently kept a backup of the original image:

cd /var/www/html/cafe/images/
ls -l
sudo mv Coffee-and-Pastries.backup Coffee-and-Pastries.jpg

A hard refresh of the Café URL showed the legitimate bakery photo again.

5.5 — Remove the IAM user

  • IAM console → Users → selected chaos → Delete (typed the user name to confirm).
  • Result: only awsstudent remained as the active IAM user.
Order matters: evict the OS session first, then harden SSH, then close the SG. Doing it in reverse order risks locking yourself out before the attacker is actually gone.

Command Reference

Quick reference of the tools and queries used during the investigation, grouped by purpose.

log

Downloading and inspecting CloudTrail logs from EC2

  • aws s3 ls — list buckets to recover the monitoring#### name.
  • aws s3 cp s3://monitoring0423/ . --recursive — pull every object from the trail bucket.
  • gunzip *.gz — CloudTrail writes each event batch as a gzipped JSON file.
  • cat <file>.json | python -m json.tool — pretty-print one event to learn the schema.
grep

Cross-file filtering with bash + grep

  • Iterate every JSON file in the current directory and print the field of interest:
for i in $(ls); do
  echo $i
  cat $i | python -m json.tool | grep sourceIPAddress
done

Same pattern works for eventName, userIdentity, etc. Useful for a first pass; breaks down once the logs grow into thousands of events.

cli

AWS CLI — cloudtrail lookup-events

  • By event name:
    aws cloudtrail lookup-events \
      --lookup-attributes AttributeKey=EventName,AttributeValue=ConsoleLogin
    
  • By resource type, scoped to a region:
    aws cloudtrail lookup-events \
      --lookup-attributes AttributeKey=ResourceType,AttributeValue=AWS::EC2::SecurityGroup \
      --region $region --output text
    
  • Narrow to a specific SG by piping into grep:
    aws cloudtrail lookup-events \
      --lookup-attributes AttributeKey=ResourceType,AttributeValue=AWS::EC2::SecurityGroup \
      --region $region --output text | grep $sgId
    
  • Region discovered straight from the IMDS on the EC2 instance:
    region=$(curl http://169.254.169.254/latest/dynamic/instance-identity/document \
      | grep region | cut -d '"' -f4)
    

    Note: this is IMDSv1. In production, IMDSv2 (token-based) is the secure default.

sql

Athena — SQL over CloudTrail

Once Athena was pointed at the CloudTrail bucket, the same investigation became a series of declarative queries instead of bash plumbing.

-- Explore the schema
SELECT useridentity.userName, eventtime, eventsource, eventname, requestparameters
FROM cloudtrail_logs_monitoring0423
LIMIT 30;

-- All users active in the last day and the distinct actions they took
SELECT DISTINCT useridentity.userName, eventName, eventSource
FROM cloudtrail_logs_monitoring0423
WHERE from_iso8601_timestamp(eventtime) > date_add('day', -1, now())
ORDER BY eventSource;

-- Tight filter: only EC2 SG-related events
SELECT useridentity.userName, eventtime, eventname, sourceIPAddress
FROM cloudtrail_logs_monitoring0423
WHERE eventsource = 'ec2.amazonaws.com'
  AND eventname LIKE '%Security%'
ORDER BY eventtime DESC;
os

OS-level eviction and hardening

  • sudo aureport --auth — recent authentication records.
  • who — currently logged-in sessions.
  • sudo userdel -r chaos-user — delete OS user and home directory.
  • sudo kill -9 <PID> — terminate the live session before delete.
  • sudo cat /etc/passwd | grep -v nologin — verify no other login-capable users remain.
  • sudo vi /etc/ssh/sshd_config → set PasswordAuthentication no.
  • sudo service sshd restart — apply the change.

Key Learnings

What the lab actually proved

CloudTrail must be on from day zero. If the trail had been enabled after the attack, the AuthorizeSecurityGroupIngress call would have left no trace and the attacker would never have been identified.
Right tool for the data size. grep works for a handful of files, aws cloudtrail lookup-events for targeted filters, and Athena once the dataset stops fitting in your terminal. Each is a step up in expressiveness.
SSH from /32 only. Any rule wider than /32 on port 22 is an instant red flag in this account model — exactly how the rogue rule was spotted by visual inspection of the SG.
Disable PasswordAuthentication. Even with a tight SG, an instance accepting password-based SSH is one credential leak away from compromise. Key-pair only is non-negotiable.
Containment order matters. Kill the live session before deleting the OS user; delete the OS user before tightening the SG; delete the IAM user once everything else is sealed. Reverse the order and you risk evicting yourself.

Technical conclusion

The lab is a compressed end-to-end IR cycle: detect → triage → identify → contain → eradicate → recover. The attacker's footprint touched both the AWS control plane (an unauthorized API call against the SG) and the instance OS (a rogue account plus a modified sshd_config). Neither half makes sense in isolation — defending only the SG would have left the OS open; cleaning only the OS would have left the SG hole behind for the next attacker.

The most valuable takeaway is not any single command but the realization that auditability is a prerequisite for investigation, not a feature added afterwards. CloudTrail is essentially free; not running it is the actual cost.

Athena is also more than a convenience: scanning ~107 KB in 652 ms with a single SQL query against the same JSON files that took multiple bash loops to skim shows why log analytics belongs in a query engine, not in a terminal pager.