Zone.Identifier: what the Mark of the Web records about a download

How Windows marks downloads in an NTFS alternate data stream: where it lives in the MFT, the zone and URL keys, hunting it at scale, and how it disappears.

When a browser saves a file on Windows, it usually leaves a small note beside it: an NTFS alternate data stream named Zone.Identifier. Windows, SmartScreen and Microsoft Office use it to decide whether to warn, block or open in Protected View. Investigators use it to answer a different question: where did this file come from?

This post covers the stream at the file-system level, what it contains, how to read it on a live system and in an image, how to hunt for it across a disk, and every way it can go missing.

Microsoft’s file system specification describes it plainly: Internet Explorer stores URL security zones in a stream named Zone.Identifier, and the fully qualified form for a file called sample.txt is sample.txt:Zone.Identifier:$DATA. The minimal content is two lines of text:

ini
[ZoneTransfer]
ZoneId=3

The feature is widely called the Mark of the Web (MOTW). Because it lives in an alternate data stream, a normal directory listing does not show it and the file’s reported size does not change.

An NTFS file’s content is its unnamed $DATA attribute (type 0x80). An alternate data stream is simply another $DATA attribute with a name, stored in the same MFT record. A Zone.Identifier stream is a few dozen to a few hundred bytes, so it is normally resident: its bytes sit inside the 1,024-byte MFT record itself rather than in clusters elsewhere on the volume.

That has two practical consequences:

  • The stream shares the file’s MFT record and timestamps. It has no $STANDARD_INFORMATION of its own, so there is no separate “stream created” time to report.
  • When the file is deleted, the stream’s text can remain in the unallocated MFT record until the record is reused, and carving MFT records for [ZoneTransfer] can recover download origins for files that are gone.

ZoneId takes a value from Windows’ URL security zone enumeration (URLZONE in Urlmon.h):

ZoneIdZoneTypical meaning for a downloaded file
0Local machineContent from the computer itself
1Local intranetContent from an intranet site
2Trusted sitesA site the user or policy marked as trusted
3InternetMost web content; the usual value for a download
4Restricted sitesA site marked as untrusted

Microsoft’s Office documentation notes that, by default, the mark is only applied to files from the Internet and Restricted sites zones, and only on NTFS volumes. A file with ZoneId=1 or 2 exists, but only where policy or the downloading application chose to write it.

Current browsers usually write more than the ZoneId line. A typical stream on a file saved by a Chromium-based browser looks like this (illustrative values):

ini
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://intranet-portal.example/announcements
HostUrl=https://files.example-cdn.net/d/7f3a/Invoice_0714.zip
KeyWhat it holds
HostUrlThe address the file was downloaded from, after redirects
ReferrerUrlThe page that linked to it
LastWriterPackageFamilyNameThe app package that wrote the stream (the Sleuth Kit’s documentation notes that Edge writes it)

These extra keys are not part of the specification quoted above, and which ones appear depends on the browser and its version, so treat them as present when you find them rather than expected. When they are there, HostUrl can answer the question that otherwise needs browser history: the exact URL a file came from.

Case note:

A HostUrl pointing to a file-sharing or paste site, on an executable that a user says they never downloaded, is the kind of detail that is worth checking against the browser’s download history and the proxy logs for the same minute.

The mark is a security control before it is an artifact:

  • SmartScreen and Attachment Manager check it before running executables and scripts, which is where the “Windows protected your PC” and “Open File - Security Warning” prompts come from.
  • Microsoft Office opens marked documents in Protected View, and since 2022 blocks VBA macros in files from the Internet zone. Microsoft’s documentation is explicit that the block applies to ZoneId=3 and not to ZoneId=2 (Trusted sites).

That is why attackers work to get rid of it, and why the absence of the mark on a malicious file is itself worth explaining. CVE-2022-41091, a Mark of the Web security feature bypass fixed in November 2022, was exploited in the wild and is listed in CISA’s Known Exploited Vulnerabilities catalog.

PowerShell
# List every stream on a file
Get-Item -Path .\Invoice_0714.zip -Stream *

# Read the Mark of the Web
Get-Content -Path .\Invoice_0714.zip -Stream Zone.Identifier

From the command prompt, dir /r lists alternate data streams next to each file, and notepad Invoice_0714.zip:Zone.Identifier opens the stream in Notepad.

Reading streams one file at a time does not scale. This collects every marked file under a path, parses the stream, and exports the result:

PowerShell
$root = 'C:\Users'
Get-ChildItem -Path $root -Recurse -File -Force -ErrorAction SilentlyContinue |
  ForEach-Object {
    $zi = Get-Content -LiteralPath $_.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue
    if ($zi) {
      $kv = @{}
      foreach ($line in $zi) { if ($line -match '^([^=\[]+)=(.*)$') { $kv[$Matches[1]] = $Matches[2] } }
      [pscustomobject]@{
        Path        = $_.FullName
        SizeBytes   = $_.Length
        Created     = $_.CreationTimeUtc
        Modified    = $_.LastWriteTimeUtc
        ZoneId      = $kv.ZoneId
        HostUrl     = $kv.HostUrl
        ReferrerUrl = $kv.ReferrerUrl
      }
    }
  } | Export-Csv -Path .\motw.csv -NoTypeInformation -Encoding utf8

Sort the CSV by Created and the result reads like a download log that survives browser history being cleared. Filter HostUrl for domains seen in the incident, or Path for executables and scripts in user-writable folders.

Caution:

Run this against a mounted image or a copy where possible. On a live system, Get-ChildItem -Recurse over a profile touches access times on directories and cannot read files that are locked or that the account has no rights to.

The Sleuth Kit addresses each attribute separately. fls lists streams as name:streamname, istat shows the record’s $DATA attributes with their IDs and names, and icat reads one by its inode-type-id address:

Shell
fls -r -p image.dd | grep ':Zone.Identifier'
istat image.dd 104233          # note the $DATA attribute ID named Zone.Identifier
icat image.dd 104233-128-5     # 128 = 0x80 ($DATA); 5 = that attribute's ID

The attribute ID differs from file to file, so read it from istat rather than assuming it. Autopsy’s Recent Activity module extracts the same keys into its results automatically.

When both survive, the mark and the browser’s own download records should agree. In Chromium-based browsers, the History SQLite database stores downloads in two tables: downloads (one row per download) and downloads_url_chains (every URL in the redirect chain, in order). Times are microseconds since 1601-01-01 UTC:

SQL
SELECT d.id,
       d.target_path,
       datetime(d.start_time / 1000000 - 11644473600, 'unixepoch') AS start_utc,
       d.referrer,
       c.chain_index,
       c.url
FROM downloads AS d
JOIN downloads_url_chains AS c ON c.id = d.id
ORDER BY d.start_time, c.chain_index;

The last URL in a chain normally corresponds to the stream’s HostUrl, and referrer to ReferrerUrl. A disagreement (a file whose HostUrl has no matching download row) suggests history was cleared, a different browser profile was used, or the file came from another program entirely.

Tip:

On hosts running Sysmon, event ID 15 (FileCreateStreamHash) records the creation of named streams, with the process that wrote it, the time, a hash of the file’s main content and the contents of the named stream. Microsoft’s description says it is aimed at exactly this case: the browser attaching a Zone.Identifier stream to a download. That gives the stream the creation timestamp NTFS never stored.

The Mark of the Web is easy to lose, and an investigator should know every way it can go missing:

  • Copying to a non-NTFS volume. FAT32 and exFAT have no alternate data streams, so the stream is dropped when the file is copied to a USB stick formatted that way, and does not come back when the file is copied home again.
  • Archives. Windows’ built-in ZIP support propagates the mark to extracted files. Third-party tools vary: 7-Zip 22.00 added a “Propagate Zone.Id stream” setting (No / Yes / For Office files) and the -snz command-line switch, so whether files extracted with 7-Zip carry the mark depends on how it was configured and invoked.
  • Container files. Disk images and other containers have been used to deliver files without the mark; CVE-2022-41091 is one fixed example of a bypass in this area.
  • Cloud sync. Microsoft’s Office documentation notes that files synced by OneDrive, files opened with “Open in Desktop App” from OneDrive or SharePoint, and files in known folders synced to OneDrive do not get the mark.
  • Unblocking. The Unblock checkbox in a file’s properties, or PowerShell’s Unblock-File, deletes the stream.
  • Other transfer paths. Files written by tools that do not use the Attachment Manager (many command-line downloaders, SMB copies, remote management tools) may never get the stream at all.

So the rule is one-directional: a Zone.Identifier stream is evidence of where a file came from; its absence is not evidence that the file was created locally.

Warning:

Like any alternate data stream, the Zone.Identifier stream is part of the file on NTFS. Acquire with a method that preserves alternate data streams (a full image, or a logical collection tool that records them), or the evidence is gone before analysis starts. Copying files out to an analysis share formatted as exFAT, or zipping them with a tool that ignores streams, destroys it silently.

Alternate data streams are not only written by Windows. An executable can be launched from a stream, and Windows then writes a prefetch record that itself lives in a stream. Eric Zimmerman’s PECmd gained an --ads switch to scan alternate data streams for hidden prefetch, and Prefetch Parser recovers the same records. The prefetch post covers that case in detail.