diff --git a/README.md b/README.md index 490b4c0e..ea3c1d55 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ The key incantations are: `-a` Skips file enumeration, just gives you a list of listable shares on the target hosts. +`-g` Skips file enumeration, just gives you a list of shares and folders on the target hosts. (combine with `-w `for the most useful results) + `-u` Makes Snaffler pull a list of account names from AD, choose the ones that look most-interesting, and then use them in a search rule. `-d` Domain to search for computers to search for shares on to search for files in. Easy. @@ -80,6 +82,8 @@ The key incantations are: `-p` Path to a directory full of .toml formatted rules. Snaffler will load all of these in place of the default ruleset. +`-w` Log everything (currently just logs directories walked but not marked for snaffling) + ## What does any of this log output mean? Hopefully this annotated example will help: diff --git a/SnaffCore/Classifiers/DirClassifier.cs b/SnaffCore/Classifiers/DirClassifier.cs index 34eb5fd2..564a1484 100644 --- a/SnaffCore/Classifiers/DirClassifier.cs +++ b/SnaffCore/Classifiers/DirClassifier.cs @@ -1,4 +1,7 @@ -using SnaffCore.Concurrency; +using System.IO; +using SnaffCore.Classifiers.EffectiveAccess; +using SnaffCore.Concurrency; +using static SnaffCore.Config.Options; namespace SnaffCore.Classifiers { @@ -20,6 +23,11 @@ public DirResult ClassifyDir(string dir) Triage = ClassifierRule.Triage, ScanDir = true, }; + + DirectoryInfo dirInfo = new DirectoryInfo(dir); + EffectivePermissions effPerms = new EffectivePermissions(MyOptions.CurrentUser); + dirResult.RwStatus = effPerms.CanRw(dirInfo); + // check if it matches TextClassifier textClassifier = new TextClassifier(ClassifierRule); TextResult textResult = textClassifier.TextMatch(dir); @@ -30,6 +38,10 @@ public DirResult ClassifyDir(string dir) { case MatchAction.Discard: dirResult.ScanDir = false; + if (MyOptions.LogEverything) + { + Mq.DirResult(dirResult); + } return dirResult; case MatchAction.Snaffle: dirResult.Triage = ClassifierRule.Triage; @@ -40,6 +52,12 @@ public DirResult ClassifyDir(string dir) return null; } } + + if (MyOptions.LogEverything) + { + Mq.DirResult(dirResult); + } + return dirResult; } } @@ -48,6 +66,7 @@ public class DirResult { public bool ScanDir { get; set; } public string DirPath { get; set; } + public RwStatus RwStatus { get; set; } public Triage Triage { get; set; } } } diff --git a/SnaffCore/Classifiers/EffectiveAccess.cs b/SnaffCore/Classifiers/EffectiveAccess.cs index 834f572a..0c7b0c37 100644 --- a/SnaffCore/Classifiers/EffectiveAccess.cs +++ b/SnaffCore/Classifiers/EffectiveAccess.cs @@ -1,5 +1,10 @@  +using System; +using System.IO; +using System.Security.AccessControl; +using System.Security.Principal; + namespace SnaffCore.Classifiers.EffectiveAccess { public class RwStatus @@ -9,4 +14,71 @@ public class RwStatus public bool CanModify { get; set; } } + public class EffectivePermissions + { + private readonly string _username; + + public EffectivePermissions(string username) + { + _username = username; + } + + public RwStatus CanRw(AuthorizationRuleCollection acl) + { + RwStatus rwStatus = new RwStatus(); + + try + { + foreach (FileSystemAccessRule rule in acl) + { + if (rule.IdentityReference.Value.Equals(_username, StringComparison.OrdinalIgnoreCase)) + { + if (((rule.FileSystemRights & FileSystemRights.Read) == FileSystemRights.Read) || + ((rule.FileSystemRights & FileSystemRights.ReadAndExecute) == FileSystemRights.ReadAndExecute) || + ((rule.FileSystemRights & FileSystemRights.ReadData) == FileSystemRights.ReadData) || + ((rule.FileSystemRights & FileSystemRights.ListDirectory) == FileSystemRights.ListDirectory)) + { + rwStatus.CanRead = true; + } + if (((rule.FileSystemRights & FileSystemRights.Write) == FileSystemRights.Write) || + ((rule.FileSystemRights & FileSystemRights.Modify) == FileSystemRights.Modify) || + ((rule.FileSystemRights & FileSystemRights.FullControl) == FileSystemRights.FullControl) || + ((rule.FileSystemRights & FileSystemRights.TakeOwnership) == FileSystemRights.TakeOwnership) || + ((rule.FileSystemRights & FileSystemRights.ChangePermissions) == FileSystemRights.ChangePermissions) || + ((rule.FileSystemRights & FileSystemRights.AppendData) == FileSystemRights.AppendData) || + ((rule.FileSystemRights & FileSystemRights.WriteData) == FileSystemRights.WriteData) || + ((rule.FileSystemRights & FileSystemRights.CreateFiles) == FileSystemRights.CreateFiles) || + ((rule.FileSystemRights & FileSystemRights.CreateDirectories) == FileSystemRights.CreateDirectories)) + { + rwStatus.CanWrite = true; + } + if (((rule.FileSystemRights & FileSystemRights.Modify) == FileSystemRights.Modify) || + ((rule.FileSystemRights & FileSystemRights.FullControl) == FileSystemRights.FullControl) || + ((rule.FileSystemRights & FileSystemRights.TakeOwnership) == FileSystemRights.TakeOwnership) || + ((rule.FileSystemRights & FileSystemRights.ChangePermissions) == FileSystemRights.ChangePermissions)) + { + rwStatus.CanModify = true; + } + } + } + } + catch (UnauthorizedAccessException) { } + + return rwStatus; + } + + public RwStatus CanRw(FileInfo fileInfo) + { + FileSecurity fileSecurity = fileInfo.GetAccessControl(); + AuthorizationRuleCollection acl = fileSecurity.GetAccessRules(true, true, typeof(NTAccount)); + return CanRw(acl); + } + + public RwStatus CanRw(DirectoryInfo dirInfo) + { + DirectorySecurity dirSecurity = dirInfo.GetAccessControl(); + AuthorizationRuleCollection acl = dirSecurity.GetAccessRules(true, true, typeof(NTAccount)); + return CanRw(acl); + } + } } diff --git a/SnaffCore/Classifiers/FileResult.cs b/SnaffCore/Classifiers/FileResult.cs index 4865eea4..72752786 100644 --- a/SnaffCore/Classifiers/FileResult.cs +++ b/SnaffCore/Classifiers/FileResult.cs @@ -14,19 +14,8 @@ public class FileResult public FileResult(FileInfo fileInfo) { - //EffectivePermissions effPerms = new EffectivePermissions(MyOptions.CurrentUser); - - // get an aggressively simplified version of the file's ACL - //this.RwStatus = effPerms.CanRw(fileInfo); - try - { - File.OpenRead(fileInfo.FullName); - this.RwStatus = new RwStatus() { CanRead = true, CanModify = false, CanWrite = false }; - } - catch (Exception e) - { - this.RwStatus = new RwStatus() { CanModify = false, CanRead = false, CanWrite = false }; - } + EffectivePermissions effPerms = new EffectivePermissions(MyOptions.CurrentUser); + this.RwStatus = effPerms.CanRw(fileInfo); // nasty debug this.FileInfo = fileInfo; @@ -56,58 +45,5 @@ public void SnaffleFile(FileInfo fileInfo, string snafflePath) Directory.CreateDirectory(snaffleDirPath); File.Copy(sourcePath, (Path.Combine(snafflePath, cleanedPath)), true); } - - /* - public static EffectivePermissions.RwStatus CanRw(FileInfo fileInfo) - { - BlockingMq Mq = BlockingMq.GetMq(); - - try - { - EffectivePermissions.RwStatus rwStatus = new EffectivePermissions.RwStatus { CanWrite = false, CanRead = false, CanModify = false }; - EffectivePermissions effPerms = new EffectivePermissions(); - string dir = fileInfo.DirectoryName; - - // we hard code this otherwise it tries to do some madness where it uses RPC with a share server to check file access, then fails if you're not admin on that host. - string hostname = "localhost"; - - string whoami = WindowsIdentity.GetCurrent().Name; - - string[] accessStrings = effPerms.GetEffectivePermissions(fileInfo, whoami); - - string[] readRights = new string[] { "Read", "ReadAndExecute", "ReadData", "ListDirectory" }; - string[] writeRights = new string[] { "Write", "Modify", "FullControl", "TakeOwnership", "ChangePermissions", "AppendData", "WriteData", "CreateFiles", "CreateDirectories" }; - string[] modifyRights = new string[] { "Modify", "FullControl", "TakeOwnership", "ChangePermissions" }; - - foreach (string access in accessStrings) - { - if (access == "FullControl") - { - rwStatus.CanModify = true; - rwStatus.CanRead = true; - rwStatus.CanWrite = true; - } - if (readRights.Contains(access)){ - rwStatus.CanRead = true; - } - if (writeRights.Contains(access)) - { - rwStatus.CanWrite = true; - } - if (modifyRights.Contains(access)) - { - rwStatus.CanModify = true; - } - } - - return rwStatus; - } - catch (Exception e) - { - Mq.Error(e.ToString()); - return new EffectivePermissions.RwStatus { CanWrite = false, CanRead = false }; ; - } - } - */ } } \ No newline at end of file diff --git a/SnaffCore/Classifiers/ShareClassifier.cs b/SnaffCore/Classifiers/ShareClassifier.cs index 4e6e7d15..153d4eec 100644 --- a/SnaffCore/Classifiers/ShareClassifier.cs +++ b/SnaffCore/Classifiers/ShareClassifier.cs @@ -1,4 +1,5 @@ -using SnaffCore.Concurrency; +using SnaffCore.Classifiers.EffectiveAccess; +using SnaffCore.Concurrency; using System; using System.IO; using static SnaffCore.Config.Options; @@ -32,11 +33,18 @@ public bool ClassifyShare(string share) // in this context snaffle means 'send a report up the queue, and scan the share further' if (IsShareReadable(share)) { + // is this supposed to be here? + DirectoryInfo shareInfo = new DirectoryInfo(share); + + EffectivePermissions effPerms = new EffectivePermissions(MyOptions.CurrentUser); + RwStatus rwStatus = effPerms.CanRw(shareInfo); + ShareResult shareResult = new ShareResult() { Triage = ClassifierRule.Triage, Listable = true, - SharePath = share + SharePath = share, + RwStatus = rwStatus }; Mq.ShareResult(shareResult); } @@ -76,9 +84,7 @@ public class ShareResult public string SharePath { get; set; } public string ShareComment { get; set; } public bool Listable { get; set; } - public bool RootWritable { get; set; } - public bool RootReadable { get; set; } - public bool RootModifyable { get; set; } + public RwStatus RwStatus { get; set; } public Triage Triage { get; set; } = Triage.Gray; } } \ No newline at end of file diff --git a/SnaffCore/Config/Options.cs b/SnaffCore/Config/Options.cs index 78c66282..c4489735 100644 --- a/SnaffCore/Config/Options.cs +++ b/SnaffCore/Config/Options.cs @@ -23,6 +23,8 @@ public partial class Options public bool ScanSysvol { get; set; } = true; public bool ScanNetlogon { get; set; } = true; public bool ScanFoundShares { get; set; } = true; + public bool ScanFoundFiles { get; set; } = true; + public bool LogEverything { get; set; } = false; public int InterestLevel { get; set; } = 0; public bool DfsOnly { get; set; } = false; public bool DfsShareDiscovery { get; set; } = false; diff --git a/SnaffCore/ShareFind/ShareFinder.cs b/SnaffCore/ShareFind/ShareFinder.cs index 4d1395c1..0732a765 100644 --- a/SnaffCore/ShareFind/ShareFinder.cs +++ b/SnaffCore/ShareFind/ShareFinder.cs @@ -19,7 +19,7 @@ public class ShareFinder private BlockingMq Mq { get; set; } private BlockingStaticTaskScheduler TreeTaskScheduler { get; set; } private TreeWalker TreeWalker { get; set; } - //private EffectivePermissions effectivePermissions { get; set; } = new EffectivePermissions(MyOptions.CurrentUser); + private EffectivePermissions EffectivePermissions { get; set; } = new EffectivePermissions(MyOptions.CurrentUser); public ShareFinder() { @@ -94,7 +94,8 @@ internal void GetComputerShares(string computer) { Listable = true, SharePath = shareName, - ShareComment = hostShareInfo.shi1_remark.ToString() + ShareComment = hostShareInfo.shi1_remark.ToString(), + RwStatus = new RwStatus() }; // Try to find this computer+share in the list of DFS targets @@ -159,26 +160,13 @@ internal void GetComputerShares(string computer) // Share is readable, report as green (the old default/min of the Triage enum ) shareResult.Triage = Triage.Green; - try - { - DirectoryInfo dirInfo = new DirectoryInfo(shareResult.SharePath); - - //EffectivePermissions.RwStatus rwStatus = effectivePermissions.CanRw(dirInfo); - - shareResult.RootModifyable = false; - shareResult.RootWritable = false; - shareResult.RootReadable = true; + DirectoryInfo dirInfo = new DirectoryInfo(shareResult.SharePath); + RwStatus rwStatus = EffectivePermissions.CanRw(dirInfo); + shareResult.RwStatus = rwStatus; - /* - if (rwStatus.CanWrite || rwStatus.CanModify) - { - triage = Triage.Yellow; - } - */ - } - catch (System.UnauthorizedAccessException e) + if (rwStatus.CanWrite || rwStatus.CanModify) { - Mq.Error("Failed to get permissions on " + shareResult.SharePath); + shareResult.Triage = Triage.Yellow; } if (MyOptions.ScanFoundShares) diff --git a/SnaffCore/TreeWalk/TreeWalker.cs b/SnaffCore/TreeWalk/TreeWalker.cs index c3cf3fc3..3be741e4 100644 --- a/SnaffCore/TreeWalk/TreeWalker.cs +++ b/SnaffCore/TreeWalk/TreeWalker.cs @@ -27,52 +27,55 @@ public void WalkTree(string currentDir) { // Walks a tree checking files and generating results as it goes. - if (!Directory.Exists(currentDir)) - { - return; - } + if (!Directory.Exists(currentDir)) + { + return; + } - try + if (MyOptions.ScanFoundFiles) { - string[] files = Directory.GetFiles(currentDir); - // check if we actually like the files - foreach (string file in files) + try { - FileTaskScheduler.New(() => + string[] files = Directory.GetFiles(currentDir); + // check if we actually like the files + foreach (string file in files) { - try + FileTaskScheduler.New(() => { - FileScanner.ScanFile(file); - } - catch (Exception e) - { - Mq.Error("Exception in FileScanner task for file " + file); - Mq.Trace(e.ToString()); - } - }); + try + { + FileScanner.ScanFile(file); + } + catch (Exception e) + { + Mq.Error("Exception in FileScanner task for file " + file); + Mq.Trace(e.ToString()); + } + }); + } + } + catch (UnauthorizedAccessException) + { + //Mq.Trace(e.ToString()); + //continue; + } + catch (DirectoryNotFoundException) + { + //Mq.Trace(e.ToString()); + //continue; + } + catch (IOException) + { + //Mq.Trace(e.ToString()); + //continue; + } + catch (Exception e) + { + Mq.Degub(e.ToString()); + //continue; } } - catch (UnauthorizedAccessException) - { - //Mq.Trace(e.ToString()); - //continue; - } - catch (DirectoryNotFoundException) - { - //Mq.Trace(e.ToString()); - //continue; - } - catch (IOException) - { - //Mq.Trace(e.ToString()); - //continue; - } - catch (Exception e) - { - Mq.Degub(e.ToString()); - //continue; - } - + try { string[] subDirs = Directory.GetDirectories(currentDir); diff --git a/Snaffler/Config.cs b/Snaffler/Config.cs index 0a7e9a57..7125d3cb 100644 --- a/Snaffler/Config.cs +++ b/Snaffler/Config.cs @@ -100,13 +100,17 @@ private static Options ParseImpl(string[] args) SwitchArgument dfsArg = new SwitchArgument('f', "dfs", "Limits Snaffler to finding file shares via DFS, for \"OPSEC\" reasons.", false); SwitchArgument findSharesOnlyArg = new SwitchArgument('a', "sharesonly", "Stops after finding shares, doesn't walk their filesystems.", false); + SwitchArgument findFoldersOnlyArg = new SwitchArgument('g', "foldersonly", + "Stops after finding folders, doesn't scan files.", false); + SwitchArgument logEverything = new SwitchArgument('w', "logeverything", + "Log everything.", false); ValueArgument compExclusionArg = new ValueArgument('k', "exclusions", "Path to a file containing a list of computers to exclude from scanning."); ValueArgument compTargetArg = new ValueArgument('n', "comptarget", "List of computers in a file(e.g C:\targets.txt), a single Computer (or comma separated list) to target."); ValueArgument ruleDirArg = new ValueArgument('p', "rulespath", "Path to a directory full of toml-formatted rules. Snaffler will load all of these in place of the default ruleset."); ValueArgument logType = new ValueArgument('t', "logtype", "Type of log you would like to output. Currently supported options are plain and JSON. Defaults to plain."); ValueArgument timeOutArg = new ValueArgument('e', "timeout", "Interval between status updates (in minutes) also acts as a timeout for AD data to be gathered via LDAP. Turn this knob up if you aren't getting any computers from AD when you run Snaffler through a proxy or other slow link. Default = 5"); - // list of letters i haven't used yet: gnqw + // list of letters i haven't used yet: q CommandLineParser.CommandLineParser parser = new CommandLineParser.CommandLineParser(); parser.Arguments.Add(timeOutArg); @@ -127,6 +131,8 @@ private static Options ParseImpl(string[] args) parser.Arguments.Add(tsvArg); parser.Arguments.Add(dfsArg); parser.Arguments.Add(findSharesOnlyArg); + parser.Arguments.Add(findFoldersOnlyArg); + parser.Arguments.Add(logEverything); parser.Arguments.Add(maxThreadsArg); parser.Arguments.Add(compTargetArg); parser.Arguments.Add(ruleDirArg); @@ -258,6 +264,16 @@ private static Options ParseImpl(string[] args) { parsedConfig.ScanFoundShares = false; } + if (findFoldersOnlyArg.Parsed) + { + parsedConfig.ScanFoundFiles = false; + } + + if (logEverything.Parsed) + { + parsedConfig.LogEverything = true; + } + if (maxThreadsArg.Parsed) { parsedConfig.MaxThreads = maxThreadsArg.Value; diff --git a/Snaffler/SnaffleRunner.cs b/Snaffler/SnaffleRunner.cs index 4d01882d..f23dfb3f 100644 --- a/Snaffler/SnaffleRunner.cs +++ b/Snaffler/SnaffleRunner.cs @@ -65,7 +65,7 @@ public void Run(string[] args) { fileResultTemplate = "{0}" + Options.Separator + "{1}" + Options.Separator + "{2}" + Options.Separator + "{3}" + Options.Separator + "{4}" + Options.Separator + "{5}" + Options.Separator + "{6}" + Options.Separator + "{7:u}" + Options.Separator + "{8}" + Options.Separator + "{9}"; shareResultTemplate = "{0}" + Options.Separator + "{1}" + Options.Separator + "{2}"; - dirResultTemplate = "{0}" + Options.Separator + "{1}"; + dirResultTemplate = "{0}" + Options.Separator + "{1}" + Options.Separator + "{3}"; } // otherwise just do the normal thing else @@ -73,7 +73,7 @@ public void Run(string[] args) // treat all as strings except LastWriteTime {6} fileResultTemplate = "{{{0}}}<{1}|{2}{3}{4}|{5}|{6}|{7:u}>({8}) {9}"; shareResultTemplate = "{{{0}}}<{1}>({2}) {3}"; - dirResultTemplate = "{{{0}}}({1})"; + dirResultTemplate = "{{{0}}}<{1}>({2})"; } //------------------------------------------ // set up new fangled logging @@ -371,15 +371,15 @@ public string ShareResultLogFromMessage(SnafflerMessage message) string shareComment = message.ShareResult.ShareComment; string rwString = ""; - if (message.ShareResult.RootReadable) + if (message.ShareResult.RwStatus.CanRead) { rwString = rwString + "R"; } - if (message.ShareResult.RootWritable) + if (message.ShareResult.RwStatus.CanWrite) { rwString = rwString + "W"; } - if (message.ShareResult.RootModifyable) + if (message.ShareResult.RwStatus.CanModify) { rwString = rwString + "M"; } @@ -391,7 +391,22 @@ public string DirResultLogFromMessage(SnafflerMessage message) { string sharePath = message.DirResult.DirPath; string triage = message.DirResult.Triage.ToString(); - return string.Format(dirResultTemplate, triage, sharePath); + + string rwString = ""; + if (message.DirResult.RwStatus.CanRead) + { + rwString = rwString + "R"; + } + if (message.DirResult.RwStatus.CanWrite) + { + rwString = rwString + "W"; + } + if (message.DirResult.RwStatus.CanModify) + { + rwString = rwString + "M"; + } + + return string.Format(dirResultTemplate, triage, sharePath, rwString); } public string FileResultLogFromMessage(SnafflerMessage message)