* Reduce number of calls to hash the analysis file.

* Add Comments to Rule File

* Clean up test.

* Try explicitly calling SHA512Managed for #602

* Add Managed Crypto Tests
This commit is contained in:
Gabe Stocco 2021-08-04 10:40:18 -07:00 коммит произвёл GitHub
Родитель 476414b133
Коммит 18768e7dac
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 167 добавлений и 78 удалений

Просмотреть файл

@ -13,7 +13,6 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
public class CryptoTests : AsaDatabaseBenchmark
{
public CryptoTests()
#nullable restore
{
}
@ -21,9 +20,13 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
[Params(100000)]
public int N { get; set; }
// The number of iterations per run
[Params(1000)]
public int NumObjects { get; set; }
// The amount of padding to add to the object in bytes Default size is approx 530 bytes serialized
// Does not include SQL overhead
[Params(0)]
[Params(1000)]
public int ObjectPadding { get; set; }
[Benchmark]
@ -31,10 +34,10 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
{
for (int i = 0; i < N; i++)
{
hashObjects.TryDequeue(out string? result);
if (result is string)
hashObjects.TryDequeue(out byte[]? result);
if (result is byte[])
{
_ = murmur128.ComputeHash(Encoding.UTF8.GetBytes(result));
_ = murmur128.ComputeHash(result);
hashObjects.Enqueue(result);
}
else
@ -49,10 +52,28 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
{
for (int i = 0; i < N; i++)
{
hashObjects.TryDequeue(out string? result);
if (result is string)
hashObjects.TryDequeue(out byte[]? result);
if (result is byte[])
{
_ = sha256.ComputeHash(Encoding.UTF8.GetBytes(result));
_ = sha256.ComputeHash(result);
hashObjects.Enqueue(result);
}
else
{
Log.Information("The queue is polluted with nulls");
}
}
}
[Benchmark]
public void Generate_N_SHA256Managed_Hashes()
{
for (int i = 0; i < N; i++)
{
hashObjects.TryDequeue(out byte[]? result);
if (result is byte[])
{
_ = sha256managed.ComputeHash(result);
hashObjects.Enqueue(result);
}
else
@ -67,10 +88,28 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
{
for (int i = 0; i < N; i++)
{
hashObjects.TryDequeue(out string? result);
if (result is string)
hashObjects.TryDequeue(out byte[]? result);
if (result is byte[])
{
_ = sha512.ComputeHash(Encoding.UTF8.GetBytes(result));
_ = sha512.ComputeHash(result);
hashObjects.Enqueue(result);
}
else
{
Log.Information("The queue is polluted with nulls");
}
}
}
[Benchmark]
public void Generate_N_SHA512_Managed_Hashes()
{
for (int i = 0; i < N; i++)
{
hashObjects.TryDequeue(out byte[]? result);
if (result is byte[])
{
_ = sha512managed.ComputeHash(result);
hashObjects.Enqueue(result);
}
else
@ -83,9 +122,9 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
[GlobalSetup]
public void GlobalSetup()
{
while (hashObjects.Count < N)
while (hashObjects.Count < NumObjects)
{
hashObjects.Enqueue(JsonConvert.SerializeObject(GetRandomObject(ObjectPadding)));
hashObjects.Enqueue(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(GetRandomObject(ObjectPadding))));
}
}
@ -93,9 +132,12 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
private static readonly HashAlgorithm sha256 = SHA256.Create();
private static readonly HashAlgorithm sha256managed = SHA256Managed.Create();
private static readonly HashAlgorithm sha512 = SHA512.Create();
private readonly ConcurrentQueue<string> hashObjects = new ConcurrentQueue<string>();
#nullable disable
private static readonly HashAlgorithm sha512managed = SHA512Managed.Create();
private readonly ConcurrentQueue<byte[]> hashObjects = new ConcurrentQueue<byte[]>();
}
}

Просмотреть файл

@ -6,7 +6,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Benchmarks
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<InsertTestsWithoutTransactions>();
var summary = BenchmarkRunner.Run<CryptoTests>();
}
}
}

Просмотреть файл

@ -126,6 +126,13 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
Environment.Exit((int)argsResult);
}
/// <summary>
/// Loads the rules from the provided file, if it is not null or empty. Or falls back to the embedded rules if it is.
/// </summary>
/// <param name="analysisFile"></param>
/// <returns>The loaded RuleFile</returns>
private static RuleFile LoadRulesFromFileOrEmbedded(string? analysisFile) => string.IsNullOrEmpty(analysisFile) ? RuleFile.LoadEmbeddedFilters() : RuleFile.FromFile(analysisFile);
private static ASA_ERROR RunGuidedModeCommand(GuidedModeCommandOptions opts)
{
opts.RunId = opts.RunId?.Trim() ?? DateTime.Now.ToString("o", CultureInfo.InvariantCulture);
@ -156,7 +163,13 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
RunCollectCommand(collectorOpts);
var analysisFile = string.IsNullOrEmpty(opts.AnalysesFile) ? RuleFile.LoadEmbeddedFilters() : RuleFile.FromFile(opts.AnalysesFile);
var analysisFile = LoadRulesFromFileOrEmbedded(opts.AnalysesFile);
if (!analysisFile.Rules.Any())
{
Log.Warning(Strings.Get("Err_NoRules"));
return ASA_ERROR.INVALID_RULES;
}
var compareOpts = new CompareCommandOptions(firstCollectRunId, secondCollectRunId)
{
@ -211,6 +224,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
{
if (opts is null) { return new ConcurrentDictionary<(RESULT_TYPE, CHANGE_TYPE), List<CompareResult>>(); }
var results = new ConcurrentDictionary<(RESULT_TYPE, CHANGE_TYPE), List<CompareResult>>();
var analysesHash = ruleFile.GetHash();
Parallel.ForEach(collectObjects, monitorResult =>
{
var shellResult = new CompareResult()
@ -220,7 +234,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
};
shellResult.Rules = analyzer.Analyze(ruleFile.Rules, shellResult).ToList();
shellResult.AnalysesHash = ruleFile.GetHash();
shellResult.AnalysesHash = analysesHash;
if (opts.ApplySubObjectRulesToMonitor)
{
@ -247,8 +261,13 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
private static ASA_ERROR RunVerifyRulesCommand(VerifyOptions opts)
{
var analyzer = new AsaAnalyzer(new AnalyzerOptions(opts.RunScripts));
var ruleFile = string.IsNullOrEmpty(opts.AnalysisFile) ? RuleFile.LoadEmbeddedFilters() : RuleFile.FromFile(opts.AnalysisFile);
var violations = analyzer.EnumerateRuleIssues(ruleFile.GetRules());
var ruleFile = LoadRulesFromFileOrEmbedded(opts.AnalysisFile);
if (!ruleFile.Rules.Any())
{
Log.Warning(Strings.Get("Err_NoRules"));
return ASA_ERROR.INVALID_RULES;
}
var violations = analyzer.EnumerateRuleIssues(ruleFile.Rules);
OAT.Utils.Strings.Setup();
OAT.Utils.Helpers.PrintViolations(violations);
if (violations.Any())
@ -469,12 +488,19 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
}
}
var ruleFile = LoadRulesFromFileOrEmbedded(opts.AnalysesFile);
if (!ruleFile.Rules.Any())
{
Log.Warning(Strings.Get("Err_NoRules"));
return ASA_ERROR.INVALID_RULES;
}
Log.Information(Strings.Get("Comparing"), opts.FirstRunId, opts.SecondRunId);
CompareCommandOptions options = new CompareCommandOptions(opts.FirstRunId, opts.SecondRunId)
{
DatabaseFilename = opts.DatabaseFilename,
AnalysesFile = string.IsNullOrEmpty(opts.AnalysesFile) ? RuleFile.LoadEmbeddedFilters() : RuleFile.FromFile(opts.AnalysesFile),
AnalysesFile = ruleFile,
DisableAnalysis = opts.DisableAnalysis,
SaveToDatabase = opts.SaveToDatabase,
RunScripts = opts.RunScripts
@ -659,10 +685,17 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
return ASA_ERROR.INVALID_ID;
}
}
var ruleFile = LoadRulesFromFileOrEmbedded(opts.AnalysesFile);
if (!ruleFile.Rules.Any())
{
Log.Warning(Strings.Get("Err_NoRules"));
return ASA_ERROR.INVALID_RULES;
}
var monitorCompareOpts = new CompareCommandOptions(null, opts.RunId)
{
DisableAnalysis = opts.DisableAnalysis,
AnalysesFile = string.IsNullOrEmpty(opts.AnalysesFile) ? RuleFile.LoadEmbeddedFilters() : RuleFile.FromFile(opts.AnalysesFile),
AnalysesFile = ruleFile,
ApplySubObjectRulesToMonitor = opts.ApplySubObjectRulesToMonitor,
RunScripts = opts.RunScripts
};
@ -883,7 +916,8 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
watch = Stopwatch.StartNew();
var analyzer = new AsaAnalyzer(new AnalyzerOptions(opts.RunScripts));
var platform = DatabaseManager.RunIdToPlatform(opts.SecondRunId);
var violations = analyzer.EnumerateRuleIssues(opts.AnalysesFile.GetRules());
var violations = analyzer.EnumerateRuleIssues(opts.AnalysesFile.Rules);
var analysesHash = opts.AnalysesFile.GetHash();
OAT.Utils.Strings.Setup();
OAT.Utils.Helpers.PrintViolations(violations);
if (violations.Any())
@ -910,7 +944,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Cli
res.Rules = analyzer.Analyze(selectedRules, res.Base, res.Compare).ToList();
res.Analysis = res.Rules.Count
> 0 ? res.Rules.Max(x => ((AsaRule)x).Flag) : opts.AnalysesFile.DefaultLevels[res.ResultType];
res.AnalysesHash = opts.AnalysesFile.GetHash();
res.AnalysesHash = analysesHash;
});
}
}

Просмотреть файл

@ -13,20 +13,48 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
{
public class RuleFile
{
public RuleFile(Dictionary<RESULT_TYPE, ANALYSIS_RESULT_TYPE>? DefaultLevels = null, List<AsaRule>? Rules = null)
/// <summary>
/// Create a RuleFile with provided DefaultLevels, Rules and Source name.
/// </summary>
/// <param name="DefaultLevels"></param>
/// <param name="Rules"></param>
/// <param name="Source"></param>
[JsonConstructor]
public RuleFile(Dictionary<RESULT_TYPE, ANALYSIS_RESULT_TYPE>? DefaultLevels = null, IEnumerable<AsaRule>? Rules = null, string? Source = null)
{
this.DefaultLevels = DefaultLevels ?? this.DefaultLevels;
this.Rules = Rules ?? new List<AsaRule>();
this.Source = Source;
}
/// <summary>
/// Create a RuleFile with the default Default Levels and null Source name.
/// </summary>
/// <param name="Rules"></param>
public RuleFile(IEnumerable<AsaRule>? Rules = null) : this(null, Rules, null)
{
}
/// <summary>
/// Create an empty RuleFile.
/// </summary>
public RuleFile()
{
}
public string? Source { get; set; } // An Identifier for the source of the rules
/// <summary>
/// An Identifier for the source of the Rules
/// </summary>
public string? Source { get; set; }
/// <summary>
/// The List of Rules
/// </summary>
public IEnumerable<AsaRule> Rules { get; set; } = new List<AsaRule>();
/// <summary>
/// The Default Levels to apply to objects if there is no corresponding rule.
/// </summary>
public Dictionary<RESULT_TYPE, ANALYSIS_RESULT_TYPE> DefaultLevels { get; set; } = new Dictionary<RESULT_TYPE, ANALYSIS_RESULT_TYPE>()
{
{ RESULT_TYPE.CERTIFICATE, ANALYSIS_RESULT_TYPE.INFORMATION },
@ -46,7 +74,13 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
{ RESULT_TYPE.FILEMONITOR, ANALYSIS_RESULT_TYPE.INFORMATION }
};
public static RuleFile FromStream(Stream? stream)
/// <summary>
/// Generate a RuleFile from a given stream containing a serialized RuleFile.
/// </summary>
/// <param name="stream">The Stream to Deserialize</param>
/// <param name="streamName">The Source Name to set in the RuleFile</param>
/// <returns></returns>
public static RuleFile FromStream(Stream? stream, string? streamName)
{
if (stream is null)
throw new NullReferenceException(nameof(stream));
@ -55,8 +89,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
using (StreamReader file = new StreamReader(stream))
{
var config = JsonConvert.DeserializeObject<RuleFile>(file.ReadToEnd());
if (config.Source is null)
config.Source = "Stream";
config.Source = streamName ?? (config.Source ?? "Stream");
return config;
}
}
@ -75,11 +108,17 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
return new RuleFile();
}
public string GetHash()
{
return CryptoHelpers.CreateHash(JsonConvert.SerializeObject(this));
}
/// <summary>
/// Get the Hash of the RuleFile
/// </summary>
/// <returns></returns>
public string GetHash() => CryptoHelpers.CreateHash(JsonConvert.SerializeObject(this));
/// <summary>
/// Load rules from a serialized RuleFile on disk.
/// </summary>
/// <param name="filterLoc"></param>
/// <returns></returns>
public static RuleFile FromFile(string? filterLoc = "")
{
if (!string.IsNullOrEmpty(filterLoc))
@ -89,8 +128,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
using (StreamReader file = System.IO.File.OpenText(filterLoc))
{
var config = JsonConvert.DeserializeObject<RuleFile>(file.ReadToEnd());
if (config.Source is null)
config.Source = filterLoc;
config.Source = filterLoc;
return config;
}
}
@ -110,6 +148,10 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
return new RuleFile();
}
/// <summary>
/// Load the default AttackSurfaceAnalyzer Rules embedded in the binary.
/// </summary>
/// <returns></returns>
public static RuleFile LoadEmbeddedFilters()
{
try
@ -117,8 +159,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
var assembly = typeof(FileSystemObject).Assembly;
var resourceName = "AttackSurfaceAnalyzer.analyses.json";
using Stream stream = assembly.GetManifestResourceStream(resourceName) ?? throw new NullReferenceException($"assembly.GetManifestResourceStream couldn't load {resourceName}");
var file = FromStream(stream);
file.Source = "Embedded Rules";
var file = FromStream(stream, "Embedded Rules");
return file;
}
catch (Exception e) when (
@ -135,20 +176,13 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Objects
return new RuleFile();
}
/// <summary>
/// Print a serialization of the filters to the verbose console.
/// </summary>
public void DumpFilters()
{
Log.Verbose("Filter dump:");
Log.Verbose(JsonConvert.SerializeObject(this));
}
public List<Rule> GetRules()
{
return Rules.Select(x => (Rule)x).ToList(); ;
}
public List<Rule> GetRulesForPlatform(PLATFORM platform)
{
return (List<Rule>)Rules.Where(x => x.Platforms.Contains(platform) || !x.Platforms.Any());
}
}
}

Просмотреть файл

@ -10,6 +10,11 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Utils
{
public static class CryptoHelpers
{
/// <summary>
/// Perform a hash of a string.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string CreateHash(string input)
{
try
@ -24,19 +29,6 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Utils
}
}
public static byte[] CreateHash(byte[] input)
{
try
{
return sha512.ComputeHash(input);
}
catch (CryptographicException e)
{
Log.Warning(e, Strings.Get("Err_CreateHash"), "bytes");
return Array.Empty<byte>();
}
}
public static string CreateHash(Stream stream)
{
try
@ -76,7 +68,6 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Utils
private static readonly RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
// These are not intended to be cryptographically secure hashes These are used as a uniqueness check
private static readonly HashAlgorithm sha512 = SHA512.Create();
private static readonly HashAlgorithm sha512 = SHA512Managed.Create();
}
}

Просмотреть файл

@ -28,7 +28,7 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Tests
{
var analyzer = new AsaAnalyzer();
var ruleFile = RuleFile.LoadEmbeddedFilters();
Assert.IsTrue(!analyzer.EnumerateRuleIssues(ruleFile.GetRules()).Any());
Assert.IsTrue(!analyzer.EnumerateRuleIssues(ruleFile.Rules).Any());
}
[TestMethod]
@ -61,13 +61,14 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Tests
var opts = new CompareCommandOptions(null, "SecondRun") { ApplySubObjectRulesToMonitor = true };
var results = AttackSurfaceAnalyzerClient.AnalyzeMonitored(opts, analyzer, new MonitorObject[] { testPathOneObject }, new RuleFile() { Rules = new AsaRule[] { andRule } });
var ruleFile = new RuleFile(new AsaRule[] { andRule });
var results = AttackSurfaceAnalyzerClient.AnalyzeMonitored(opts, analyzer, new MonitorObject[] { testPathOneObject }, ruleFile);
Assert.IsTrue(results.Any(x => x.Value.Any(y => y.Identity == testPathOneObject.Identity && y.Rules.Contains(andRule))));
opts = new CompareCommandOptions(null, "SecondRun") { ApplySubObjectRulesToMonitor = false };
results = AttackSurfaceAnalyzerClient.AnalyzeMonitored(opts, analyzer, new MonitorObject[] { testPathOneObject }, new RuleFile() { Rules = new AsaRule[] { andRule } });
results = AttackSurfaceAnalyzerClient.AnalyzeMonitored(opts, analyzer, new MonitorObject[] { testPathOneObject }, ruleFile);
Assert.IsFalse(results.Any(x => x.Value.Any(y => y.Identity == testPathOneObject.Identity && y.Rules.Contains(andRule))));
}
@ -75,18 +76,5 @@ namespace Microsoft.CST.AttackSurfaceAnalyzer.Tests
private const string TestPathOne = "TestPath1";
private readonly FileMonitorObject testPathOneObject = new FileMonitorObject(TestPathOne) { FileSystemObject = new FileSystemObject(TestPathOne) { IsExecutable = true } };
private Analyzer GetAnalyzerForRule(AsaRule rule)
{
var file = new RuleFile()
{
Rules = new List<AsaRule>()
{
rule
}
};
return new AsaAnalyzer();
}
}
}