This Sherlock case involves a phishing attack where an attacker impersonates a known vendor to deceive an accounting team. The email, designed to appear legitimate, includes an urgent payment request and a ZIP attachment containing malware. By analyzing the email’s headers and content, investigators can identify the phishing tactics used and the potential risks involved. This case underscores the need for careful scrutiny of unexpected emails and attachments to prevent security breaches.
An attacker posing as a known vendor emailed an accounting team with an urgent payment request. The message came from a domain the attacker controlled, so SPF passed, while the Reply-To quietly pointed at a different mailbox on the same domain. The body linked to a lookalike subdomain and carried a ZIP “invoice” whose only member was a batch script wearing a fake PDF extension. The archive was never extracted or run, so the script’s behaviour is not established, but the delivery chain is a standard attachment phish built to get a finance user to double-click.
.eml file in evidence/ → read with Python’s email module. Headers, the HTML body and the base64 attachment all come out reproducibly this wayTimestamp note: No question asks for a time, so none are tabulated here.
Dump the headers and look for the sender’s own address rather than a relay. The X-Originating-IP header holds it, and it differs from the address on the Received chain, which is the next question.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print(m['X-Originating-IP'])" evidence/email.eml
Read the Received headers top to bottom. The topmost one is the final hop, mail.business-finance.com handing the message to mail.target.com, and the IP on that hop is the relaying server.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print('\n'.join(m.get_all('Received')))" evidence/email.eml
Gotcha: Two IPs sit in the headers.
X-Originating-IPis the sender (Q1); the relay IP comes from theReceivedline.
From and Return-Path agree, so there is no envelope/header split to untangle.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print(m['From'], m['Return-Path'])" evidence/email.eml
Compare Reply-To against From. They are different mailboxes on the same domain, so a reply never reaches the address the message appears to come from. That mismatch is the tell.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print(m['Reply-To'])" evidence/email.eml
Received-SPF and Authentication-Results agree. The result is a pass, which only says the sending server is authorised for the domain the attacker owns. It does not say the sender is who they claim to be.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print(m['Received-SPF']); print(m['Authentication-Results'])" evidence/email.eml
Tip: Do not read a passing auth verdict as a trust signal. Judge the domain itself.
Pull the href out of the HTML body part. The link sits on a secure. subdomain of the sender’s own domain, which makes it look consistent with the From address.
python3 -I -c "import email,re,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print(re.findall(r'href=[\"\\']([^\"\\']+)', m.get_body(('html',)).get_content()))" evidence/email.eml
Gotcha: The format hint says “domain name”, but the registrable domain alone was rejected. The answer is the full host including the
secure.label.
The name appears in the message signature and repeats in an X-Organization header, so it is consistent across the message.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print(m['X-Organization'])" evidence/email.eml
Walk the MIME parts and read the Content-Disposition filename of the attachment part.
python3 -I -c "import email,sys; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); print([p.get_filename() for p in m.iter_attachments()])" evidence/email.eml
Decode the base64 attachment part and hash the decoded bytes, not the base64 text. The decoded file is only 75 bytes, which is the first sign the archive is truncated (Q10).
python3 -I -c "import email,sys,hashlib; from email import policy; m=email.message_from_file(open(sys.argv[1]),policy=policy.default); a=next(m.iter_attachments()).get_content(); print(len(a), hashlib.sha256(a).hexdigest())" evidence/email.eml
A malicious file delivered as an email attachment is Phishing: Spearphishing Attachment. The format hint shows a single T****.*** ID, so the sub-technique is the answer, not the parent.
unzip -l fails because the base64 in the email is cut short, so the central directory is missing. The local file header at the start of the archive is intact though: the filename length sits at offset 26 and the name follows the 30-byte fixed header. Reading it by hand gives a batch file hiding behind a fake PDF extension. It was identified by name only and never extracted, so its behaviour is not known from this evidence.
python3 -I -c "import struct,sys; d=open(sys.argv[1],'rb').read(); n=struct.unpack('<H',d[26:28])[0]; print(d[30:30+n].decode())" work/att.zip
Tip: When a ZIP will not open, the first local header still gives you the member name without any extraction.
| Phase | Action |
|---|---|
| Initial Access | Vendor-impersonation email sent from attacker-controlled domain, SPF passes |
| Initial Access | Reply-To set to a different mailbox than From |
| Initial Access | Body links to a secure. lookalike subdomain with an urgent payment request |
| Initial Access | ZIP “invoice” attached |
| Defense Evasion | ZIP member is a batch file with a fake .pdf extension (execution not observed) |
| Type | Value |
|---|---|
| Sender IP | 45.67.89[.]10 |
| Relay IP | 203.0.113[.]25 |
| Sender address | finance@business-finance[.]com |
| Reply-To | support@business-finance[.]com |
| Link host | secure[.]business-finance[.]com |
| Attachment | Invoice_2025_Payment.zip |
| SHA-256 (attachment) | 8379c41239e9af845b2ab6c27a7509ae8804d7d73e455c800a551b22ba25bb4a |
| ZIP member | invoice_document.pdf.bat |
| Technique | ID | Description |
|---|---|---|
| Phishing: Spearphishing Attachment | T1566.001 | ZIP attachment carrying a script in an invoice email |
| Masquerading: Double File Extension | T1036.007 | Batch file named to look like a PDF |
A passing SPF result is not a trust signal. The attacker registered their own domain and published valid SPF for it, so every auth check passed. Score the sending domain’s age and similarity to known vendors, not just the verdict.
Alert on a Reply-To that differs from From. It is a cheap, high-signal mail rule, especially for finance and accounting mailboxes where payment fraud is the goal.
Quarantine archives containing script files and double-extension names. A .pdf.bat member inside a ZIP should never reach an inbox. Block .bat, .cmd and similar inside archives at the gateway.
Check the full link host, not just the domain. A secure. subdomain on a new vendor domain reads as legitimate. Rewrite and detonate links, and show users the real host.
Make payment changes a process control. Urgent requests from new or changed vendor details need out-of-band confirmation before money moves.
The email carried no exploit and needed none: valid SPF, a plausible vendor domain and a ZIP with a PDF-looking batch file were enough to put a payment lure and a script in front of an accounting team. The detection value was in the header mismatches and the attachment name, not in any authentication result.