01Session plan
| Minutes | Block | What happens |
|---|---|---|
| 0–15 | Brief, rules and integrity (Q01) | Read export-metadata.txt aloud: which bucket has data events, which time formats appear, which source is local time. |
| 15–50 | The key outside (Q02–Q05) | Filter on the access key ID, not the user. Convert the notice to UTC and count the refusals. |
| 50–95 | Into the bucket (Q06–Q09) | Follow the temporary key from AssumeRole. Count keys, not rows; separate the refused reads; check the access log agrees. |
| 95–130 | Out of the account (Q10–Q15) | Find the public policy, then switch to the access log where CloudTrail is silent. Find the second key and read the refused StopLogging. |
| 130–150 | The signed sentence (Q16) | Each trainee writes the finding and the containment it requires, then compares it with sentence c and the model finding. |
Materials: the pack files on each trainee's machine, a printed worksheet from the pack page, and an examination log form. Pack page →
02Answer key
Q011 point
On a copy of cloudtrail-events.jsonl, compute the SHA-256 value, compare it with manifest.txt, and enter the first 8 hex characters.
Answer9f0d1571
The prefix matches the manifest line. Pretty-printing the JSON or re-sorting the lines changes the bytes; hash the file as it was exported.
- Where
- manifest.txt, the cloudtrail-events.jsonl line.
- Usual mistake
- Hashing a copy after a viewer has reformatted the JSON.
Hash calculatorF-04 Examination logSOP-02 Evidence intake and registration
Q022 points
When did ci-deploy's key AKIAEXAMPLECIDEPLOY1 first sign a request from an address other than the runner 192.0.2.40? UTC.
Answer2026-08-07 21:03:12
Filter userIdentity.accessKeyId on the key. Every record before 21:03:12 comes from 192.0.2.40 with the Terraform user agent, three runs a day. At 21:03:12 sts:GetCallerIdentity comes from 203.0.113.50 with the AWS CLI on Ubuntu: the usual first call of someone who has found a key and wants to know whose it is.
- Where
- cloudtrail-events.jsonl, userIdentity.accessKeyId AKIAEXAMPLECIDEPLOY1, sourceIPAddress; acquisition-log.txt, the runner address.
- Usual mistake
- Filtering on userName ci-deploy and taking a later record of the role session, or the second key's use the next morning.
Q032 points
How many whole minutes passed between the push that published the key and that first outside request?
Answer4
The notice gives the push at 07.08.2026 23:58:41 in the code host account's time zone, Europe/Istanbul (UTC+3): 20:58:41 UTC. The first outside request is at 21:03:12 UTC: 4 minutes 31 seconds later. Keys in public repositories are collected by automated scanners within minutes; the detection e-mail came 42 minutes after the push, the key was already in use.
- Where
- gizli-anahtar-uyarisi.txt, 'Commit gönderimi' and the time zone line; cloudtrail-events.jsonl, the record of Q02.
- Usual mistake
- Reading 23:58:41 as UTC, which puts the push almost three hours after the first use.
Q042 points
How many requests signed with AKIAEXAMPLECIDEPLOY1 from 203.0.113.50 were refused (the record carries an errorCode)?
Answer12
Ten refusals during the enumeration between 21:03 and 21:11 (iam:ListUsers, s3:ListBuckets, iam:GetAccountAuthorizationDetails, iam:ListGroupsForUser, secretsmanager:ListSecrets, ec2:DescribeInstances in four regions with Client.UnauthorizedOperation, iam:CreateUser) and two later ones (cloudtrail:StopLogging and DeleteTrail). The user's own policy held; the role it could assume did not have the same limits. Refusals in four regions within minutes are themselves a sign of someone mapping an account.
- Where
- cloudtrail-events.jsonl, accessKeyId AKIAEXAMPLECIDEPLOY1, sourceIPAddress 203.0.113.50, errorCode present.
- Usual mistake
- Counting the 24 refused GetObject requests too: they were signed with the role session's key, not the user's.
Q051 point
Which IAM role did the outside address assume with the user's key?
Answerlodos-data-ops
sts:AssumeRole at 21:11:27, roleArn arn:aws:iam::111122223333:role/lodos-data-ops, roleSessionName backup-audit. The pipeline assumes the same role in every run, with session names terraform-ci-<run>; the trust policy that lets ci-deploy assume it is what turned a narrow key into a wide one.
- Where
- cloudtrail-events.jsonl, eventName AssumeRole from 203.0.113.50, requestParameters.roleArn.
- Usual mistake
- Taking the portal's role from the AWSService records, which EC2 assumes every hour.
Q062 points
Which temporary access key ID did that role session receive?
AnswerASIAEXAMPLEDATAOPS01
responseElements.credentials.accessKeyId of the AssumeRole record. From here on the records carry userIdentity.type AssumedRole, the ARN …assumed-role/lodos-data-ops/backup-audit and this key, not the user's. The expiration 'Aug 7, 2026, 10:11:27 PM' is UTC written without a zone: the session could sign requests until 22:11:27.
- Where
- cloudtrail-events.jsonl, the AssumeRole record of Q05, responseElements.
- Usual mistake
- Searching the rest of the file for the user's key and concluding that the bulk reads were made by someone else.
Q072 points
The GetObject records of the night name the role, not ci-deploy. What ties them to the leaked key?
AnswerC — Their userIdentity.accessKeyId is the temporary key issued in the AssumeRole record that ci-deploy's key signed from 203.0.113.50 at 21:11:27, and they come from the same address with the same user agent.
The chain is key → AssumeRole → temporary key → records. A session name is chosen by the caller and the next morning's session uses it again, so it groups, it does not prove. The portal role reads the same bucket all day through the VPC endpoint; the bucket alone separates nothing.
- Where
- cloudtrail-events.jsonl, responseElements.credentials.accessKeyId of AssumeRole and userIdentity.accessKeyId of the GetObject records.
- Usual mistake
- Treating an assumed role as a dead end.
Q082 points
How many distinct objects did the session ASIAEXAMPLEDATAOPS01 read successfully from lodos-musteri-arsiv?
Answer294
The session has 330 GetObject records: 306 succeeded and 24 were refused. Twelve of the successes read a key a second time, so the distinct keys are 294, all under musteri-sozlesmeleri/. The access log of the bucket shows the same 330 requests from the same requester ARN with the same 294 keys answered 200; the bytes sent agree with CloudTrail's bytesTransferredOut (388,180,218 bytes).
- Where
- cloudtrail-events.jsonl, eventName GetObject, userIdentity.accessKeyId ASIAEXAMPLEDATAOPS01, no errorCode, requestParameters.key; s3-access-logs.log, the same requester.
- Usual mistake
- Giving 330 records or 306 successes, or counting all GetObject records of the night including the portal's.
Q092 points
The session's 24 GetObject requests under kimlik-belgeleri/ were refused with an errorMessage about kms:Decrypt. What should the report say?
AnswerB — That 24 identity documents were requested and not returned: they are encrypted with a KMS key the role may not use. They count as attempted, not as disclosed.
errorCode AccessDenied with bytesTransferredOut 0, and the access log answers 403 for the same keys. A separate key policy is what kept these documents in; the report must say both that they were targeted and that they were not delivered.
- Where
- cloudtrail-events.jsonl, GetObject with errorCode; s3-access-logs.log, status 403 for the same keys.
- Usual mistake
- Adding the refused requests to the number of disclosed documents.
Q102 points
When did the session make lodos-yedek-paylasim readable by anyone? Give the time of the call that did it, UTC.
Answer2026-08-07 21:59:30
At 21:58:44 DeletePublicAccessBlock removed the bucket's Block Public Access setting; until then a public policy would have been rejected. At 21:59:30 PutBucketPolicy set Principal '*' with s3:GetObject on disa-aktarim/*. The second call is the one that opened the bucket; the first made it possible.
- Where
- cloudtrail-events.jsonl, eventName DeletePublicAccessBlock and PutBucketPolicy, requestParameters.bucketPolicy.
- Usual mistake
- Giving 21:58:44, which removed the guard but granted nothing.
Q112 points
CloudTrail has no PutObject into lodos-yedek-paylasim during the night. What do you conclude?
AnswerC — Data events are selected for lodos-musteri-arsiv only, so uploads to this bucket cannot appear in CloudTrail. Its server access log records three PUT requests by the session from 22:03:15, the parts of disa-aktarim/arsiv-0807.
Absence in a source proves nothing until the source's scope says it would have recorded the act. export-metadata.txt states the data event selector; the access log fills the gap and names the requester ARN …assumed-role/lodos-data-ops/backup-audit. The parts are 734,003,200, 734,003,200 and 389,521,408 bytes.
- Where
- export-metadata.txt, export 1, data events; s3-access-logs.log, REST.PUT.OBJECT in lodos-yedek-paylasim.
- Usual mistake
- Reading 'not in CloudTrail' as 'did not happen'.
Q122 points
When was a part of the archive first downloaded without authentication? UTC.
Answer2026-08-07 22:20:41
Requester '-' and authentication type '-' with REST.GET.OBJECT and status 200 on disa-aktarim/: the first is [07/Aug/2026:22:20:41 +0000] from 198.51.100.200 with Wget. The earlier unauthenticated request at 22:07:02 from 203.0.113.50 is a HEAD: it checked that the object was reachable and transferred no content.
- Where
- s3-access-logs.log, bucket lodos-yedek-paylasim, requester '-', operation REST.GET.OBJECT.
- Usual mistake
- Giving the HEAD at 22:07:02, or an auditor's presigned download, which is authenticated (QueryString).
Q132 points
From how many distinct addresses were the archive parts downloaded without authentication?
Answer3
198.51.100.200 (all three parts, from 22:20:41), 203.0.113.90 (all three, from 02:10:17 on 8 August) and 198.51.100.201 (part 1 twice, from 03:44:52). The session's own address made only the HEAD. The leak did not stop when the session ended: the bucket stayed public until containment at 09:05.
- Where
- s3-access-logs.log, lodos-yedek-paylasim, requester '-', REST.GET.OBJECT, status 200, remote IP.
- Usual mistake
- Counting the auditor at 198.51.100.150, whose downloads were presigned by m.koc, or adding 203.0.113.50 for its HEAD.
Q142 points
Which access key did the leaked key create for ci-deploy?
AnswerAKIAEXAMPLECIDEPLOY2
iam:CreateAccessKey at 22:14:02 from 203.0.113.50, responseElements.accessKey.accessKeyId. It was used at 06:12:33 on 8 August from 203.0.113.51 to assume the role again. The credential report agrees: access_key_2_last_rotated 2026-08-07T22:14:02+00:00, last used 2026-08-08T06:15:02+00:00. Rotating the first key alone would have left this one open.
- Where
- cloudtrail-events.jsonl, eventName CreateAccessKey; iam-credential-report.csv, the ci-deploy row.
- Usual mistake
- Reading access_key_1_last_used_date (06:10:34, the pipeline's last run from 192.0.2.40) as the attacker's last use.
Q152 points
At 22:21:40 the leaked key called cloudtrail:StopLogging on lodos-org-trail. What happened to logging?
AnswerB — It continued: the call was refused with AccessDenied, as was DeleteTrail after it, and the trail keeps recording through the next morning.
Refused calls are recorded with errorCode and errorMessage. More than two hundred records follow the attempt, including the second key's activity at 06:12. The attempt is still a finding: it shows intent to remove the record, and belongs in the report as such.
- Where
- cloudtrail-events.jsonl, eventName StopLogging and DeleteTrail, errorCode; the records after them.
- Usual mistake
- Treating an attempt as an outcome.
Q162 points
Which sentence could the examiner sign from these files alone?
AnswerC — Four minutes after the key AKIAEXAMPLECIDEPLOY1 was pushed to a public repository (20:58:41 UTC on 7 August), it signed requests from 203.0.113.50, assumed lodos-data-ops and the session ASIAEXAMPLEDATAOPS01 read 294 contracts from lodos-musteri-arsiv; 24 identity documents were refused. The session made lodos-yedek-paylasim public at 21:59:30 and uploaded a three-part archive that three addresses downloaded without authentication from 22:20:41. The key created AKIAEXAMPLECIDEPLOY2, used at 06:12 on 8 August from 203.0.113.51; its attempt to stop the trail was refused.
Sentence c ties each act to a key, a session and a record, converts the notice to UTC, separates read from refused, uses the access log where CloudTrail is silent and names the second key. Sentence a names a person for requests from an outside address; b contradicts the refused StopLogging; d ignores the data event scope and the access log.
- Where
- All four evidence files; each fact in sentence c comes from one of them.
- Usual mistake
- Writing who the key belongs to instead of who used it.
03Reconstructed timeline
The timeline as the correct answers build it. Local time is UTC+3.
| UTC | Local | Event | Source |
|---|---|---|---|
| 21:03:12 | 00:03:12 | First request signed with AKIAEXAMPLECIDEPLOY1 from 203.0.113.50: sts:GetCallerIdentity, 4 minutes after the push. | cloudtrail-events.jsonl |
| 21:11:27 | 00:11:27 | AssumeRole lodos-data-ops, session backup-audit, temporary key ASIAEXAMPLEDATAOPS01. | cloudtrail-events.jsonl |
| 21:15:10 | 00:15:10 | Bulk GetObject from lodos-musteri-arsiv begins: 294 contracts read, 24 identity documents refused. | cloudtrail-events.jsonl |
| 21:59:30 | 00:59:30 | PutBucketPolicy makes lodos-yedek-paylasim/disa-aktarim/ readable by anyone. | cloudtrail-events.jsonl |
| 22:03:15 | 01:03:15 | First part of the archive uploaded to the public bucket (access log only). | s3-access-logs.log |
| 22:14:02 | 01:14:02 | CreateAccessKey: AKIAEXAMPLECIDEPLOY2 for ci-deploy. | cloudtrail-events.jsonl |
| 22:20:41 | 01:20:41 | First anonymous download of the archive, from 198.51.100.200. | s3-access-logs.log |
| 22:21:40 | 01:21:40 | StopLogging on lodos-org-trail refused (AccessDenied). | cloudtrail-events.jsonl |
| 06:12:33 | 09:12:33 | The second key is used from 203.0.113.51 and assumes the role again. | cloudtrail-events.jsonl |
04Scoring
Questions 1–11 carry one point each and question 12 carries three: 30 points in all. The finding paragraph is not scored by points; mark it against the four criteria below and discuss it in the debrief.
| Points | Next step |
|---|---|
| 27–30 | Sound. Move on to a supervised review of a real account's CloudTrail Lake or Athena queries. |
| 15–26 | Review each missed question with its explanation, then rebuild the night in the workbench filtered on the two access keys. |
| 0–14 | Work through VP-07 first, then repeat this pack with the workbench pivoted on ASIAEXAMPLEDATAOPS01. |
Finding paragraph: criteria
- Every time carries its basis; the notice is converted from local time.
- The report follows access key IDs through AssumeRole and says how the role session is tied to the leaked key.
- Objects are counted by key and only where the request succeeded; refused requests are reported as attempts.
- The report states the data event scope and uses the access log for the bucket CloudTrail does not cover.
- Containment covers both keys, the role's trust policy, the public bucket and the three downloading addresses.
05Debrief
- A key in a public repository is found by scanners in minutes, not days. Detection that arrives in forty minutes arrives late.
- A narrow user with the right to assume a wide role is a wide user.
- CloudTrail is complete only for what it was told to record. Read the trail's selectors before you read its silence.
- Refusals are evidence: they map what someone tried, and where the account's limits held.
06Model finding
On 7 August 2026 at 20:58:41 UTC (23:58:41 local in the code host's notice) the access key AKIAEXAMPLECIDEPLOY1 of the IAM user ci-deploy was pushed to a public repository. From 21:03:12 UTC the key signed requests from 203.0.113.50 with the AWS CLI; until then it had been used only from the build runner 192.0.2.40 (cloudtrail-events.jsonl). Twelve of those requests were refused, ten of them during enumeration across four regions. At 21:11:27 the key assumed the role lodos-data-ops (session backup-audit, temporary key ASIAEXAMPLEDATAOPS01). That session read 294 distinct objects under musteri-sozlesmeleri/ in lodos-musteri-arsiv between 21:15:10 and 21:47:19 (306 successful GetObject records, 388,180,218 bytes); 24 requests for identity documents encrypted with a KMS key were refused. At 21:58:44 and 21:59:30 it removed Block Public Access from lodos-yedek-paylasim and set a policy granting s3:GetObject on disa-aktarim/* to everyone. The bucket's server access log records three uploads by the session from 22:03:15 (arsiv-0807-part1 to part3; data events do not cover this bucket) and downloads without authentication from 198.51.100.200, 203.0.113.90 and 198.51.100.201 from 22:20:41 (s3-access-logs.log). At 22:14:02 the leaked key created AKIAEXAMPLECIDEPLOY2, which was used from 203.0.113.51 at 06:12:33 on 8 August to assume the role again (credential report: last used 06:15:02). Its attempts to stop and delete the trail at 22:21:40 were refused. Limits: the repository, the outside hosts and the objects' contents were not examined.