Skip to content

BuildNot yet confirmed elsewhere1 publisher3 min readPublished

GuardDuty says exfiltration and the patch is four hours out: revoke the sessions first

A time-based deny on aws:TokenIssueTime invalidates every credential the role already handed out while the running fleet refreshes through it. The stolen session outlives the patch window by 90 minutes.

The Engineer · Build desk

How we use AISend a correction

Illustration accompanying GuardDuty says exfiltration and the patch is four hours out: revoke the sessions first
Generated illustration

What happened

  • GuardDuty raises Exfiltration:S3/AnomalousBehavior after an attacker pulls the instance role's temporary credentials from metadata and starts bulk-downloading a sensitive bucket.
  • The development team's fix is four hours away, so the response has to work without any code change.
  • The first action is put-role-policy attaching an inline deny named AWSRevokeOlderSessions, denying all actions where aws:TokenIssueTime is earlier than the current UTC time.
  • Confirmation is get-role-policy for the attachment, then a CloudTrail lookup for AccessDenied on S3 GetObject after the cutoff timestamp.
  • The walkthrough rejects the empty security group as a fix, on the grounds that the attacker's S3 calls originate from an entirely different source IP.

Why it matters

  • capability Revoking by issuance time means you can cut the attacker off without knowing which key he took, where he is calling from, or how many copies exist.
  • exposure Anyone who chooses to wait for the developers is exposed for the full remaining life of the stolen session, which in this example runs ninety minutes past the patch.
  • constraint The bucket-wide deny is off the table during business hours: its blast radius is every principal in the account, so it trades a data read for an application outage.
  • decision Revoke and isolate stop being alternatives; skipping isolation leaves the metadata harvest open, so the deny buys one credential cycle and nothing beyond it.

The condition key is doing all the work. aws:TokenIssueTime is a property of the session rather than of the caller [16], so the inline policy sorts credentials by when they were minted and takes no interest in who holds them or which network they are calling from. That is why one document can be both a kill switch and safe for production: the attacker's copy was issued before the cutoff and dies on its next request, while each instance in the fleet asks the metadata service for a fresh set and gets one stamped after the cutoff [1]. The walkthrough notes the deny is evaluated on every request, so there is no propagation window to sit through [16].

The arithmetic is what rules out waiting. The sample credentials in the post expire at 16:00Z against a revoke timestamp of 10:30Z [5]. That is five and a half hours of remaining validity on a session someone has already copied off the box, measured against a four-hour patch estimate [6]; the stolen session outlives the fix by ninety minutes [15]. Doing nothing until the developers ship is not a slower version of the same outcome.

Revocation is also not containment. The same auto-refresh that keeps legitimate callers alive is available to anyone still sitting on the instance, because harvesting is a curl against 169.254.169.254 and a copy of three values [13], and post-cutoff credentials satisfy the condition by construction [1][9]. So the two moves the post separates are complementary: swapping the instance into an empty security group does not stop the exfiltration, since the S3 calls come from a different source IP entirely [12], but leaving the instance reachable means the deny buys one round of credentials and no more.

The bucket policy is the option to keep filed away. A Deny on s3:GetObject with Principal "*" on the bucket does stop the attacker, and it equally stops the application servers reading patient data, the backup jobs, the analytics pipelines, and every other IAM principal in the account [14]. That trade converts a read of sensitive data into a full outage of everything that touches it, which is a worse incident report than the one you started with.

The unglamorous part is the discovery hop at the top. You need describe-instances against the instance ID from the finding to get the instance profile ARN, then get-instance-profile to resolve the role name [7]. In the example the profile and the role are both ProdDataAccessRole and the second call looks redundant, which is exactly the assumption that will cost time on a role where they differ. Then put-role-policy attaches the inline deny under the name AWSRevokeOlderSessions [4]; the console's Revoke Sessions tab writes the same document for you [8], which matters mostly because it is one fewer hand-typed UTC timestamp at the point in the night where a typo sets the cutoff in the wrong direction.

Validation is two calls: get-role-policy to confirm the document landed, and a CloudTrail lookup for AccessDenied on s3.amazonaws.com after the cutoff [10], plus a head-object from a healthy instance on the same role to prove production still reads [11].

What to watch

  • Whether anything consuming that role holds credentials without a refresh loop, such as a set exported into an operator's shell: those consumers break rather than self-heal, which turns the revoke...
  • Whether the compromised identity is an instance profile at all; for a pod or function role the describe-instances to get-instance-profile hop does not exist and role discovery has to come from...
  • Whether CloudTrail actually shows AccessDenied after the cutoff, because an empty result means either the attacker stopped or he re-harvested post-revocation credentials, and those two read very...

Clarity's read

What the record supports and how the coverage leans. The claims behind it follow.

Reality

Evidence42
Adoption
Insufficient
Hype gap+12
Incentives22
Confidence40
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [1]

    An EC2 instance that has not been isolated automatically requests new credentials from the metadata service; those new credentials carry a TokenIssueTime after the revocation timestamp and so pass the condition check.

  2. [2]

    In the scenario, an EC2 instance with an attached IAM role holding s3:GetObject on a sensitive bucket is compromised; the attacker extracts temporary credentials from the instance metadata service and begins bulk-downloading objects.

  3. [3]

    GuardDuty fires the finding Exfiltration:S3/AnomalousBehavior.

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · August 23, 2026

    # Stopping S3 Data Exfiltration in Real Time: A Step-by-Step Incident Response

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Topics

  • S3 Data ExfiltrationFollow
  • Cloud Detection and Audit TrailsFollow
  • Cloud Incident ResponseFollow
  • Instance Metadata Credential TheftFollow
  • AWS IAM Session RevocationFollow
Loading related stories