Learn · Network forensics

E-mail: read it from the bottom

An e-mail carries its own travel log. Every server that handles it adds a line on top. Read those lines bottom-up, check who wrote each one, and you can tell where a message really came from, and when.

1 Envelope, header, body

SMTP transports a message with an envelope (MAIL FROM, RCPT TO) that the servers use for delivery. The header (From:, To:, Subject:, Date:) is part of the message and is written by the sender's program; nothing forces it to match the envelope. That is why From: alone proves nothing. A .eml file is the message as stored: header lines, an empty line, the body.

2 The Received chain

Each server adds Received: from X by Y with Z id …; date on top of the header. The oldest line is at the bottom. In each line, by is the server that wrote it, and from is what the previous host claimed to be, followed in brackets by the address the writing server actually saw. Only lines written by servers you trust (your own organisation's) are reliable; a sender can put forged Received lines below them.

Header analyser

Three servers wrote three different zone notations (-0500, +0100, +0000), but in UTC the hops are seconds apart, as they should be. Large gaps or times going backwards point to wrong clocks, queued mail, or forged lines. The bottom line also shows the sender's private address (192.168.1.20) and the public address their router used (203.0.113.77).

3 SPF, DKIM, DMARC

Three DNS-based checks, recorded by the receiving server in Authentication-Results:

The SPF record is just a DNS answer. Here is the TXT answer for example.net, and the MX answer that says which servers receive mail for it:

For the report: DNS records change. Record the SPF, DKIM and DMARC records as they were at the time of the message if you can (passive DNS, archived zone files), not only as they are today.

4 MIME, base64 and encoded words

Attachments and non-ASCII text are encoded. A multipart/mixed message is split by a boundary string into parts, each with its own Content-Type, Content-Transfer-Encoding (base64 or quoted-printable) and, for attachments, Content-Disposition: attachment; filename=…. Headers with umlauts use encoded words such as =?utf-8?Q?Gr=C3=BC=C3=9Fe?=. Decode, then hash each attachment: the hash links it to copies found elsewhere.

Decoder

On the server side, mail systems keep a message tracking log: one line per step (receive, deliver, fail), with time, sender, recipients, subject, size and Message-ID, often for 30 days or more. It covers messages that were deleted from mailboxes, and messages that were blocked and never delivered.

5 Try it yourself

Use the analyser and the hex views above.

From which public IP address did the sender's computer hand the example message to the first server?

The bottom Received line, the address in brackets after from.

203.0.113.77, as seen and recorded by relay.example.net. The [192.168.1.20] before it is what the client announced (its private LAN address); it is only a claim.

How many seconds passed between the first and the last Received hop of the example message?

Convert all three times to UTC first.

03:00:59 −0500 = 08:00:59 UTC; 09:01:05 +0100 = 08:01:05 UTC; 08:01:07 UTC. 08:01:07 − 08:00:59 = 8 seconds.

Which network does the SPF record of example.net allow to send mail directly?

Read the TXT answer: the ip4: mechanism.

198.51.100.0/24, plus whatever _spf.mailhost.example lists (include:). -all means everything else fails. The relay in the example (198.51.100.30) is inside the range, so SPF passes.

Which Received line of a message in your own mailbox can you trust the most?

Who could have written each line?

The top one. Your own server wrote it. Every line below came from servers outside your control, and the lowest ones could have been written by the sender. Received lines are not signed.

These are the bytes of the SPF TXT record: a length byte, then the text. Edit them and watch the decoded answer:

  • Change 2D (the "-" before all, the last but three byte) to 7E ("~"). What does ~all mean for mail from other servers?
  • Change the first byte (the length, 3D = 61) to 10. What does a resolver now read as the record, and what happens to the rest?