VP-08 · Instructor notes

A leaked AWS key and a storage leak: CloudTrail, S3 access logs and a public bucket

Everything needed to run the pack with a group: a 150 minutes plan, the answer to each question with where it is found and the usual mistake, the timeline the answers build, scoring and the points to close on.

This page contains the answers. Trainees should work from the pack page first.

01Session plan

MinutesBlockWhat happens
0–15Brief, rules and integrity (Q01)Read export-metadata.txt aloud: which bucket has data events, which time formats appear, which source is local time.
15–50The key outside (Q02–Q05)Filter on the access key ID, not the user. Convert the notice to UTC and count the refusals.
50–95Into the bucket (Q06–Q09)Follow the temporary key from AssumeRole. Count keys, not rows; separate the refused reads; check the access log agrees.
95–130Out 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–150The 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

  1. 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

  2. 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.
  3. 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.

    Local time to UTC

  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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'.
  12. 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).
  13. 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.
  14. 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.
  15. 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.
  16. 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.

    F-04 Examination logSOP-09 Reporting and peer review

03Reconstructed timeline

The timeline as the correct answers build it. Local time is UTC+3.

UTCLocalEventSource
21:03:1200:03:12First request signed with AKIAEXAMPLECIDEPLOY1 from 203.0.113.50: sts:GetCallerIdentity, 4 minutes after the push.cloudtrail-events.jsonl
21:11:2700:11:27AssumeRole lodos-data-ops, session backup-audit, temporary key ASIAEXAMPLEDATAOPS01.cloudtrail-events.jsonl
21:15:1000:15:10Bulk GetObject from lodos-musteri-arsiv begins: 294 contracts read, 24 identity documents refused.cloudtrail-events.jsonl
21:59:3000:59:30PutBucketPolicy makes lodos-yedek-paylasim/disa-aktarim/ readable by anyone.cloudtrail-events.jsonl
22:03:1501:03:15First part of the archive uploaded to the public bucket (access log only).s3-access-logs.log
22:14:0201:14:02CreateAccessKey: AKIAEXAMPLECIDEPLOY2 for ci-deploy.cloudtrail-events.jsonl
22:20:4101:20:41First anonymous download of the archive, from 198.51.100.200.s3-access-logs.log
22:21:4001:21:40StopLogging on lodos-org-trail refused (AccessDenied).cloudtrail-events.jsonl
06:12:3309:12:33The 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.

PointsNext step
27–30Sound. Move on to a supervised review of a real account's CloudTrail Lake or Athena queries.
15–26Review each missed question with its explanation, then rebuild the night in the workbench filtered on the two access keys.
0–14Work 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

  1. A key in a public repository is found by scanners in minutes, not days. Detection that arrives in forty minutes arrives late.
  2. A narrow user with the right to assume a wide role is a wide user.
  3. CloudTrail is complete only for what it was told to record. Read the trail's selectors before you read its silence.
  4. 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.

Report anatomy and confidence scale →

Contact and service requests

Contact us about examination services related to this page, support for expert witnesses, or collaboration. Please do not attach evidence files or personal data to your email.

Contact page →