Skip to content

Commit

Permalink
Add file output option and tweak for STIX 2.0 validator conformance
Browse files Browse the repository at this point in the history
  • Loading branch information
Sean McElroy committed Sep 22, 2018
1 parent 84f0268 commit 5679d06
Showing 1 changed file with 82 additions and 26 deletions.
108 changes: 82 additions & 26 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
Expand All @@ -18,6 +19,8 @@ namespace guardduty_stix
{
class Program
{
private static readonly SHA256Managed sha256managed = new SHA256Managed();

public class Options
{
[Option('p', "profile", Required = false, HelpText = "Connect to the AWS account with the credential stored in a named profile")]
Expand All @@ -31,6 +34,9 @@ public class Options

[Option('r', "region", Required = false, HelpText = "Instead of a profile, use the specified AWS region", Default = "us-east-1")]
public string Region { get; set; }

[Option('o', "output", Required = false, HelpText = "Instead of dumping to stdout, save to the specified file")]
public string OutputFile { get; set; }
}

private static string[] titleBanner = new string[] {
Expand Down Expand Up @@ -61,6 +67,8 @@ public class Options
@" --secret=ACCESS_KEY_SECRET Instead of a profile, use the specified AWS access secret",
@" --region=AWS-REGION-1 Specify the region for the connection. Required if profile not specified",
@"",
@" --output=FILE_PATH If specified, will save output to specified file; otherwise, to stdout",
@"",
};

static int Main(string[] args)
Expand Down Expand Up @@ -133,13 +141,20 @@ static int Main(string[] args)

var cts = new CancellationTokenSource();

var getFindingsTask = Task.Run(async () =>
var getFindingsTask = Task.Run(new Func<Task<Tuple<object, Exception>>>(async () =>
{
var client = new AmazonGuardDutyClient(awsCredentials, awsRegion);
var detectorRequest = new ListDetectorsRequest();
var detectorResponse = await client.ListDetectorsAsync(detectorRequest, cts.Token);
dynamic bundle = new ExpandoObject();
bundle.type = "bundle";
bundle.id = $"guardduty-stix-{DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", System.Globalization.CultureInfo.InvariantCulture)}";
bundle.spec_version = "2.0";
var objects = new List<object>();
foreach (var detectorId in detectorResponse.DetectorIds)
{
var listFindingsRequest = new ListFindingsRequest()
Expand All @@ -164,51 +179,89 @@ static int Main(string[] args)
};
var getFindingsResponse = await client.GetFindingsAsync(getFindingsRequest, cts.Token);
dynamic bundle = new ExpandoObject();
bundle.type = "bundle";
bundle.id = $"{detectorId}-{DateTime.UtcNow.ToString("o")}";
bundle.spec_version = "2.0";
var objects = new List<object>();
foreach (var finding in getFindingsResponse.Findings)
{
var sdo = await ConvertFindingToStixAsync(finding);
objects.Add(sdo);
}
bundle.objects = objects;
await Console.Out.WriteLineAsync(Newtonsoft.Json.JsonConvert.SerializeObject(bundle));
}
catch (Exception e)
{
await Console.Error.WriteLineAsync(e.ToString());
return new Tuple<object, Exception>(null, e);
}
}
});
Task.WaitAll(new[] { getFindingsTask }, 60000, cts.Token);
bundle.objects = objects;
return new Tuple<object, Exception>(bundle, null);
}));

if (!Task.WaitAll(new[] { getFindingsTask }, 60000, cts.Token))
{
Console.Error.WriteLine("Failed to complete within 60 seconds, aborted.");
System.Environment.Exit(-7);
return -7;
}

var result = getFindingsTask.Result;

if (result.Item2 != null)
{
Console.Error.WriteLine($"Unable to parse output: {result.Item2.ToString()}");
System.Environment.Exit(-8);
return -8;
}

if (string.IsNullOrWhiteSpace(options.OutputFile))
Console.Out.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(result.Item1));
else
{
try
{
using (var fs = new FileStream(options.OutputFile, FileMode.Create, FileAccess.Write))
using (var sw = new StreamWriter(fs))
{
sw.Write(Newtonsoft.Json.JsonConvert.SerializeObject(result.Item1));
}

Console.Out.WriteLine($"Output saved to file {options.OutputFile}");
}
catch (Exception e)
{
Console.Error.WriteLine($"Unable to write file: {e.ToString()}");
System.Environment.Exit(-9);
return -9;
}
}

return 0;
}

private static async Task<object> ConvertFindingToStixAsync(Finding finding)
{
// UUID should be deterministically determined from finding.Id
byte[] bytes = Encoding.UTF8.GetBytes(finding.Id);
var hashstring = new SHA256Managed();
var hash = hashstring.ComputeHash(bytes);
var bytes = Encoding.UTF8.GetBytes(finding.Id);
var hash = sha256managed.ComputeHash(bytes);
var uuid = new Guid(hash.Take(16).ToArray());

var labels = new object[0];

dynamic ret = new ExpandoObject();
ret.id = $"indicator--{uuid}";
ret.type = "indicator";
ret.name = finding.Title;
ret.description = finding.Description;
ret.valid_from = DateTime.Parse(finding.CreatedAt).ToString("o");
ret.created = DateTime.Parse(finding.CreatedAt).ToString("o");
ret.modified = DateTime.Parse(finding.UpdatedAt).ToString("o");
if (finding.Title != null)
ret.name = finding.Title;
if (finding.Description != null)
ret.description = finding.Description;
if (finding.CreatedAt != null && DateTime.TryParse(finding.CreatedAt, out DateTime dateCreatedAt))
{
ret.valid_from = dateCreatedAt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", System.Globalization.CultureInfo.InvariantCulture);
ret.created = ret.valid_from;
}

if (finding.UpdatedAt != null && DateTime.TryParse(finding.UpdatedAt, out DateTime dateUpdatedAt))
ret.modified = dateUpdatedAt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", System.Globalization.CultureInfo.InvariantCulture);

ret.external_references = new[]
{
new {
Expand Down Expand Up @@ -267,15 +320,17 @@ private static async Task<object> ConvertFindingToStixAsync(Finding finding)
subPattern.Append(" OR ");
subPattern.Append($"({(subPattern.Length > 2 ? " AND " : string.Empty)}(network-traffic:dst_ref.type = 'ipv4-addr' AND network-traffic:dst_ref.value = '{nic.PublicIp ?? nic.PrivateIpAddress}/32'))");
}
if (nicCount < 2) {
if (nicCount < 2)
{
subPattern.Remove(0, 2);
subPattern.Remove(subPattern.Length - 2, 1);
}
else
subPattern.Append(')');
sbPattern.Append(subPattern);
}
else if (finding.Title.StartsWith("Outbound portscan from EC2 instance")) {
else if (finding.Title.StartsWith("Outbound portscan from EC2 instance"))
{
var subPattern = new StringBuilder();
subPattern.Append('(');
var nicCount = 0;
Expand All @@ -286,7 +341,8 @@ private static async Task<object> ConvertFindingToStixAsync(Finding finding)
subPattern.Append(" OR ");
subPattern.Append($"({(subPattern.Length > 2 ? " AND " : string.Empty)}(network-traffic:src_ref.type = 'ipv4-addr' AND network-traffic:src_ref.value = '{nic.PublicIp ?? nic.PrivateIpAddress}/32'))");
}
if (nicCount < 2) {
if (nicCount < 2)
{
subPattern.Remove(0, 2);
subPattern.Remove(subPattern.Length - 2, 1);
}
Expand Down Expand Up @@ -325,7 +381,7 @@ private static async Task<object> ConvertFindingToStixAsync(Finding finding)
sbPattern.Append($"{(sbPattern.Length > 1 ? " AND " : string.Empty)}({asn}network-traffic:src_ref.type = 'ipv4-addr' AND network-traffic:src_ref.value = '{probDetail.RemoteIpDetails.IpAddressV4}/32')");
}
}

if (sbPattern.Length > 1)
ret.pattern = sbPattern.Append("]").ToString();

Expand Down

0 comments on commit 5679d06

Please sign in to comment.