セキュリティログを監視し、ファイルシステムへのアクセスをモニタする

Windows では .NET Framework の FileSystemWatcher クラス を使うとファイルシステムの変更をモニタすることが出来ます。しかし、FileSystemWatcher クラスでは「ファイルやフォルダを操作したユーザが分からない」という、大きな欠点があるようです。

そこで、代替手段として Windows のセキュリティ監査の新機能 などに記載のある「オブジェクトアクセスの監査」機能を使い、記録されたセキュリティログをモニタし、ファイルシステムへの操作を監視することが出来ます(この方法であれば、ファイルやフォルダを操作したユーザを取得することが可能です。スマートとは言えませんが...)。イベントログは EventLog クラスEventWritten イベント によってモニタすることが出来ます。ただし、Vista / 2003 以前と Windows 7 / 2008 ではオブジェクトアクセス時に発生するイベントの ID が以下のように異なるようで、やや注意が必要です。

イベント Vista / 2003 以前 Windows 7 / 2008
ファイルオープン 560 4656
ファイルアクセス 567 4663
ファイルコピー (N/A) 4690
ファイル削除 564 4660
ファイルクローズ 562 4658

ローカルコンピュータのセキュリティログをモニタする、簡単なサンプルを C# で作ってみました。例外処理も無く、Windows 7 / 2008 限定版です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System;
using System.Diagnostics;
using System.IO;
using System.Net;

namespace EventLogWatchSample
{
  class Program
  {
    private const int INDEX = 10;
    private const string NOT_FOUND = "(Not found)";

    static void Main(string[] args)
    {
      var elog = new EventLog("Security", ".");
      elog.EnableRaisingEvents = true;
      elog.EntryWritten += new EntryWrittenEventHandler(EventLogEntryWritten);

      // キー入力があれば、アプリケーションを終了
      Console.WriteLine("*** Press any key to exit. ***");
      Console.Read();
    }

    private static void EventLogEntryWritten(
      object sender, System.Diagnostics.EntryWrittenEventArgs e)
    {
      // Windows 7 / Windows Server 2008 における以下のイベント ID を監視
      //
      //   4656 ファイルオープン    4663 ファイルアクセス    4690 ファイルコピー
      //   4660 ファイル削除        4658 ファイルクローズ
      //
      var id = e.Entry.InstanceId;
      if (id == 4656 || id == 4663 || id == 4690 || id == 4690 || id == 4658)
      {
        string aName = GetValue(e.Entry.Message, "アカウント名");
        string oName = GetValue(e.Entry.Message, "オブジェクト名");

        // 以下のアクセスはログ監視の対象から除外
        //
        //   (1) システム(ローカルマシン)からのアクセス
        //   (2) 対象オブジェクト名の無いログ
        //   (3) レジストリへのアクセス
        //
        if (aName == Dns.GetHostName() + "$" || oName == NOT_FOUND || oName.StartsWith("\\REGISTRY"))
          return;

        Console.WriteLine(
          "---- EventID:" + e.Entry.InstanceId + " (" + DateTime.Now.ToString() + ") -----\n" +
          "  AccountName : " + aName + "\n" +
          "  ObjectName  : " + oName + "\n");
      }
    }

    private static string GetValue(string entry, string keyword)
    {
      string line = null;
      string value = NOT_FOUND;
      using (var sr = new StringReader(entry))
      {
        while ((line = sr.ReadLine()) != null)
        {
          if (line.IndexOf(keyword) != -1)
          {
            value = line.Substring(INDEX);
            sr.Close();
            break;
          }
        }
        sr.Close();
      }
      return value;
    }
  }
}