Wednesday, July 13, 2011

Detecting and Changing a File’s Internet Zone in .NET: Alternate Data Streams

I spent most of yesterday investigating some weird behaviour in MEF, which I’ll discuss in another post. I was saved by Twitter in the guise of @Grumpydev, @jordanterrell and @SQLChap who came to the rescue and led me down a very interesting rabbit hole, to a world of URL Zones and Alternate Data Streams. Thanks chaps!

If you download a file from the internet on Windows 2003 or later, right click, and select properties, you’ll see something like this:


The file is ‘blocked’ which means that you will get various dialogues if you try to say, run an executable with this flag set.

Any file on NTFS can have a ‘Zone’ as the flag is called. The values are described in this enumeration:

typedef enum tagURLZONE {

The Zone is not standard security information stored in the file’s ACL. Instead it uses a little known feature of NTFS, ‘Alternate Data Streams’ (ADS).

Sysinternals provide a command line utility streams.exe that you can use to inspect and remove ADSs, including the Zone flag, on a file or a whole directory tree of files.

You can access a file’s Zone in .NET by using the System.Security.Policy.Zone class. Like this:

var zone = Zone.CreateFromUrl("file:///C:/temp/ZoneTest.doc");
if (zone.SecurityZone != SecurityZone.MyComputer)
Console.WriteLine("File is blocked");
Console.Out.WriteLine("zone.SecurityZone = {0}", zone.SecurityZone);

If you want to create, view and delate ADSs in .NET you will need to resort to pInvoke, there is no support for them in the BCL. Luckily for us, Richard Deeming, has done the work for us and created a set of classes that wrap the NTFS API. You can read about it here and get the code from GitHub here.

Using Richard’s library, you can list the ADSs for a file and their values like this:

var fileInfo = new FileInfo(path);

foreach (var alternateDataStream in fileInfo.ListAlternateDataStreams())
Console.WriteLine("{0} - {1}", alternateDataStream.Name, alternateDataStream.Size);

// Read the "Zone.Identifier" stream, if it exists:
if (fileInfo.AlternateDataStreamExists("Zone.Identifier"))
Console.WriteLine("Found zone identifier stream:");

var s = fileInfo.GetAlternateDataStream("Zone.Identifier",FileMode.Open);
using (TextReader reader = s.OpenText())
Console.WriteLine("No zone identifier stream found.");

When I run this against a file downloaded from the internet I get this output:

Zone.Identifier - 26
Found zone identifier stream:

You can see that the ZoneId = 3, so this file’s Zone is URLZONE_INTERNET.

You can delete an ADS like this:

var fileInfo = new FileInfo(path);

And lastly you can set the ZoneId like this. Here I’m changing a file to have a internet zone:

var fileInfo = new FileInfo(path);

var ads = new AlternateDataStreamInfo(path, "Zone.Identifier", null, false);
using(var stream = ads.OpenWrite())
using(var writer = new StreamWriter(stream))

ADSs are very interesting, and open up a whole load of possibilities. Imagine storing application specific metadata in an ADS for example. I’d be very interested to hear if anyone has used them in this way.


Jo and Dave said... - quite interesting on ADS

SQLChap said...

You can play about with the alternative streams in Notepad or a command line

Mike Hadlow said...

Thanks chaps! Great links.

Anonymous said...

I know that Windows Server that supports File Sharing for Macintosh uses the streams for application metadata that is stored in resource forks by OS X.

Anonymous said...

If you want to manipulate the value, you're better off using the PersistentZoneIdentifier COM object. Now you don't have to care about NTFS at all!