Skip to content

This tool is not affiliated with, endorsed by or sponsored by Amazon Web Services, Inc. or Amazon.com, Inc. AWS, Amazon Web Services, CloudTrail and GuardDuty are trademarks of Amazon.com, Inc. or its affiliates. Other names are trademarks of their respective owners.

VPC Flow Logs analysis for exfiltration and mining

Reading VPC Flow Logs in an investigation: key fields, egress volume per destination, mining-pool ports, what flow logs never record and the data traps.

Published on 5 min read

TL;DR. Flow logs are network metadata: who talked to whom, on which port, how many bytes, accepted or rejected — per network interface, aggregated over one to ten minutes. For an investigation, three questions pay off: how many bytes left a private address for each public destination (exfiltration), which instances hold long-lived connections to pool-like ports (mining), and which external addresses reached SSH/RDP successfully (access). They do not contain payloads, DNS queries to the Amazon resolver or instance-metadata traffic, and S3 downloads made from the internet never cross your VPC.

CloudTrail tells you what was configured. VPC Flow Logs tell you what the resources then did on the network. When an attacker lands on an instance — through a stolen key, a vulnerable app or a new instance they launched — the flow logs are often the only record of where the data went.

The default record

The default format is version 2 (AWS documentation):

version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status
2 111122223333 eni-0f9e8d7c6b5a40000 10.20.1.11 192.0.2.150 40000 3333 6 42 8123 1789379520 1789379579 ACCEPT OK
FieldMeaning in an investigation
interface-idThe ENI — map it to an instance with DescribeNetworkInterfaces or CloudTrail RunInstances responses
srcaddr / dstaddrFor egress, srcaddr is the interface's private address
dstport, protocol6 = TCP, 17 = UDP
bytes, packetsPer record, for that aggregation window only
start, endUnix seconds
actionACCEPT or REJECT (security group / NACL)
log-statusOK, NODATA (no traffic), SKIPDATA (records skipped)

Custom formats can add vpc-id, instance-id, tcp-flags, pkt-srcaddr / pkt-dstaddr (the original addresses behind NAT or secondary IPs), flow-direction and traffic-path. When you receive files from S3, the first line names the fields — keep it, because the column order depends on the format chosen when the flow log was created.

Question 1: did data leave?

Filter ACCEPT records where the source is private (RFC 1918) and the destination public, then sum bytes per (source, destination) pair over the whole incident window. Exfiltration is a volume question, and the volume is spread over many records because each covers at most ten minutes (one minute or less on Nitro instances).

What to look for:

  • a single public destination receiving gigabytes from an instance that normally talks to a database and a load balancer;
  • transfers at odd hours, or starting minutes after an attacker's SSH session;
  • destinations in hosting providers rather than your known partners.

Then check what else explains it: backups to an external service, software updates, a CDN origin pull. The mapping is MITRE ATT&CK T1048 Exfiltration Over Alternative Protocol — the protocol is invisible to flow logs, only volume and ports are.

Question 2: is something mining?

Mining traffic is the opposite of exfiltration: small, steady, endless. Signs:

  • every instance launched in the incident keeps an ACCEPTed connection to the same one or two public addresses;
  • destination ports used by mining pools (3333, 4444, 5555, 7777, 14444 and similar);
  • similar byte counts minute after minute, in both directions.

Pools listening on 443 exist, so port alone is not proof; the regularity and the timing relative to the RunInstances events are. See crypto-mining incident response.

Question 3: who got in?

Inbound ACCEPT on 22 or 3389 from public addresses to an instance tells you which external hosts reached a shell port. A REJECT flood shows scanning; a single ACCEPT from the same address that later appears in CloudTrail as the source of API calls ties the two sources together.

Mapping interfaces to instances

Flow logs name network interfaces, not instances. To tie a flow to a CloudTrail story:

  1. Take the interface-id from the suspicious records.
  2. Look it up with aws ec2 describe-network-interfaces --network-interface-ids eni-…, which returns the attached instance and its private addresses — if the instance still exists.
  3. If it was terminated, search CloudTrail for the RunInstances response containing that interface ID or private address; the response lists networkInterfaceSet for each instance launched.
  4. From the instance, pivot to the principal that launched it and its instance profile.

Add instance-id to custom flow log formats when you create new ones: it saves this step during the next incident.

What flow logs will never show you

According to AWS's list of flow log limitations, these are not logged:

  • traffic to the Amazon DNS server (so no DNS exfiltration through the VPC resolver — use Route 53 Resolver query logs);
  • traffic to 169.254.169.254 (instance metadata — credential theft from IMDS leaves no flow);
  • Amazon Time Sync, DHCP, Windows license activation, ARP, traffic to the default VPC router's reserved address;
  • mirrored traffic on the source side.

Also: flow logs apply only from creation onward, cannot be edited (a new format needs a new flow log), may skip records under load (SKIPDATA), and do not see S3 or other AWS API downloads made from the internet — those never enter your VPC. For S3, use data events or server access logs.

Traps in the data

  • Two records per conversation. One per direction per interface; don't double-count bytes when summing both.
  • NAT gateways. Behind a NAT gateway, the instance's flows show the NAT's interface; use pkt-srcaddr / pkt-dstaddr if the format has them.
  • Time windows. start/end bound the aggregation window, not the TCP session.
  • Default VPC in unused regions. Attackers launch there because nobody enabled flow logs there. Absence of logs is not absence of traffic.

In the analyzer

AWS Forensics reads default and custom-format flow logs (header-aware, pkt-*addr aware, NODATA/SKIPDATA skipped) and flags more than 1 GiB accepted from a private address to a single internet host and accepted outbound connections to known mining-pool ports, with the interfaces and destinations as parameters. Flow-log evidence lands on the same timeline as CloudTrail, so the SSH connection, the RunInstances call and the first pool connection line up. For how to collect them, see the export guide.

Related articles

A fictional AWS incident investigated from its logs: leaked key, recon, backdoor admin, GuardDuty deleted, 320 S3 objects taken, GPU mining in Singapore.
GPU instances, a bill spike, a region nobody uses: confirm crypto-mining on AWS from CloudTrail and flow logs, contain it, and find how they got in.
Prove or rule out S3 data theft: CloudTrail data events, S3 server access logs, public bucket policies, shared snapshots, and what you cannot see without them.

This tool is not affiliated with, endorsed by or sponsored by Amazon Web Services, Inc. or Amazon.com, Inc. AWS, Amazon Web Services, CloudTrail and GuardDuty are trademarks of Amazon.com, Inc. or its affiliates. Other names are trademarks of their respective owners.