Add virus total scanner

This commit is contained in:
Kieran 2022-03-07 16:29:52 +00:00
parent cff3b70403
commit 8340916c15
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
6 changed files with 458 additions and 16 deletions

View File

@ -5,42 +5,55 @@ namespace VoidCat.Model
public class VoidSettings
{
public string DataDirectory { get; init; } = "./data";
public TorSettings? TorSettings { get; init; }
public JwtSettings JwtSettings { get; init; } = new("void_cat_internal", "default_key_void_cat_host");
public JwtSettings JwtSettings { get; init; } = new()
{
Issuer = "void_cat_internal",
Key = "default_key_void_cat_host"
};
public string? Redis { get; init; }
public StrikeApiSettings? Strike { get; init; }
public SmtpSettings? Smtp { get; init; }
public List<Uri> CorsOrigins { get; init; } = new();
public CloudStorageSettings? CloudStorage { get; init; }
public VirusScannerSettings? VirusScanner { get; init; }
}
public sealed record TorSettings(Uri TorControl, string PrivateKey, string ControlPassword);
public sealed class TorSettings
{
public Uri TorControl { get; init; }
public string PrivateKey { get; init; }
public string ControlPassword { get; init; }
}
public sealed record JwtSettings(string Issuer, string Key);
public sealed class JwtSettings
{
public string Issuer { get; init; }
public string Key { get; init; }
}
public sealed record SmtpSettings
public sealed class SmtpSettings
{
public Uri? Server { get; init; }
public string? Username { get; init; }
public string? Password { get; init; }
}
public sealed record CloudStorageSettings
public sealed class CloudStorageSettings
{
public bool ServeFromCloud { get; init; }
public S3BlobConfig? S3 { get; set; }
}
public sealed record S3BlobConfig
public sealed class S3BlobConfig
{
public string? AccessKey { get; init; }
public string? SecretKey { get; init; }
@ -49,14 +62,20 @@ namespace VoidCat.Model
public string? BucketName { get; init; } = "void-cat";
}
public sealed record VirusScannerSettings
public sealed class VirusScannerSettings
{
public ClamAVSettings? ClamAV { get; init; }
public ClamAVSettings? ClamAV { get; init; }
public VirusTotalConfig? VirusTotal { get; init; }
}
public sealed record ClamAVSettings
public sealed class ClamAVSettings
{
public Uri? Endpoint { get; init; }
public long? MaxStreamSize { get; init; }
}
}
public sealed class VirusTotalConfig
{
public string? ApiKey { get; init; }
}
}

View File

@ -47,6 +47,7 @@ if (useRedis)
services.AddSingleton(cx.GetDatabase());
}
services.AddHttpClient();
services.AddCors(opt =>
{
opt.AddDefaultPolicy(p =>

View File

@ -1,6 +1,7 @@
using nClam;
using VoidCat.Model;
using VoidCat.Services.Abstractions;
using VoidCat.Services.VirusScanner.VirusTotal;
namespace VoidCat.Services.VirusScanner;
@ -25,6 +26,14 @@ public static class VirusScannerStartup
});
services.AddTransient<IVirusScanner, ClamAvScanner>();
}
// load VirusTotal
if (avSettings.VirusTotal != default)
{
services.AddTransient((svc) =>
new VirusTotalClient(svc.GetRequiredService<IHttpClientFactory>(), avSettings.VirusTotal));
services.AddTransient<IVirusScanner, VirusTotalScanner>();
}
}
}
}

View File

@ -0,0 +1,327 @@
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace VoidCat.Services.VirusScanner.VirusTotal;
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class AlertContext
{
[JsonProperty("proto")] public string Proto { get; set; }
[JsonProperty("src_ip")] public string SrcIp { get; set; }
[JsonProperty("src_port")] public int SrcPort { get; set; }
}
public class CrowdsourcedIdsResult
{
[JsonProperty("alert_context")] public List<AlertContext> AlertContext { get; set; }
[JsonProperty("alert_severity")] public string AlertSeverity { get; set; }
[JsonProperty("rule_category")] public string RuleCategory { get; set; }
[JsonProperty("rule_id")] public string RuleId { get; set; }
[JsonProperty("rule_msg")] public string RuleMsg { get; set; }
[JsonProperty("rule_source")] public string RuleSource { get; set; }
}
public class CrowdsourcedIdsStats
{
[JsonProperty("high")] public int High { get; set; }
[JsonProperty("info")] public int Info { get; set; }
[JsonProperty("low")] public int Low { get; set; }
[JsonProperty("medium")] public int Medium { get; set; }
}
public class CrowdsourcedYaraResult
{
[JsonProperty("description")] public string Description { get; set; }
[JsonProperty("match_in_subfile")] public bool MatchInSubfile { get; set; }
[JsonProperty("rule_name")] public string RuleName { get; set; }
[JsonProperty("ruleset_id")] public string RulesetId { get; set; }
[JsonProperty("ruleset_name")] public string RulesetName { get; set; }
[JsonProperty("source")] public string Source { get; set; }
}
public class ALYac
{
[JsonProperty("category")] public string Category { get; set; }
[JsonProperty("engine_name")] public string EngineName { get; set; }
[JsonProperty("engine_update")] public string EngineUpdate { get; set; }
[JsonProperty("engine_version")] public string EngineVersion { get; set; }
[JsonProperty("method")] public string Method { get; set; }
[JsonProperty("result")] public string Result { get; set; }
}
public class APEX
{
[JsonProperty("category")] public string Category { get; set; }
[JsonProperty("engine_name")] public string EngineName { get; set; }
[JsonProperty("engine_update")] public string EngineUpdate { get; set; }
[JsonProperty("engine_version")] public string EngineVersion { get; set; }
[JsonProperty("method")] public string Method { get; set; }
[JsonProperty("result")] public string Result { get; set; }
}
public class AVG
{
[JsonProperty("category")] public string Category { get; set; }
[JsonProperty("engine_name")] public string EngineName { get; set; }
[JsonProperty("engine_update")] public string EngineUpdate { get; set; }
[JsonProperty("engine_version")] public string EngineVersion { get; set; }
[JsonProperty("method")] public string Method { get; set; }
[JsonProperty("result")] public string Result { get; set; }
}
public class Acronis
{
[JsonProperty("category")] public string Category { get; set; }
[JsonProperty("engine_name")] public string EngineName { get; set; }
[JsonProperty("engine_update")] public string EngineUpdate { get; set; }
[JsonProperty("engine_version")] public string EngineVersion { get; set; }
[JsonProperty("method")] public string Method { get; set; }
[JsonProperty("result")] public object Result { get; set; }
}
public class LastAnalysisResults
{
[JsonProperty("ALYac")] public ALYac ALYac { get; set; }
[JsonProperty("APEX")] public APEX APEX { get; set; }
[JsonProperty("AVG")] public AVG AVG { get; set; }
[JsonProperty("Acronis")] public Acronis Acronis { get; set; }
}
public class LastAnalysisStats
{
[JsonProperty("confirmed-timeout")] public int ConfirmedTimeout { get; set; }
[JsonProperty("failure")] public int Failure { get; set; }
[JsonProperty("harmless")] public int Harmless { get; set; }
[JsonProperty("malicious")] public int Malicious { get; set; }
[JsonProperty("suspicious")] public int Suspicious { get; set; }
[JsonProperty("timeout")] public int Timeout { get; set; }
[JsonProperty("type-unsupported")] public int TypeUnsupported { get; set; }
[JsonProperty("undetected")] public int Undetected { get; set; }
}
public class VirusTotalJujubox
{
[JsonProperty("category")] public string Category { get; set; }
[JsonProperty("confidence")] public int Confidence { get; set; }
[JsonProperty("malware_classification")]
public List<string> MalwareClassification { get; set; }
[JsonProperty("malware_names")] public List<string> MalwareNames { get; set; }
[JsonProperty("sandbox_name")] public string SandboxName { get; set; }
}
public class SandboxVerdicts
{
[JsonProperty("VirusTotal Jujubox")] public VirusTotalJujubox VirusTotalJujubox { get; set; }
}
public class SigmaAnalysisStats
{
[JsonProperty("critical")] public int Critical { get; set; }
[JsonProperty("high")] public int High { get; set; }
[JsonProperty("low")] public int Low { get; set; }
[JsonProperty("medium")] public int Medium { get; set; }
}
public class SigmaIntegratedRuleSetGitHub
{
[JsonProperty("critical")] public int Critical { get; set; }
[JsonProperty("high")] public int High { get; set; }
[JsonProperty("low")] public int Low { get; set; }
[JsonProperty("medium")] public int Medium { get; set; }
}
public class SigmaAnalysisSummary
{
[JsonProperty("Sigma Integrated Rule Set (GitHub)")]
public SigmaIntegratedRuleSetGitHub SigmaIntegratedRuleSetGitHub { get; set; }
}
public class TotalVotes
{
[JsonProperty("harmless")] public int Harmless { get; set; }
[JsonProperty("malicious")] public int Malicious { get; set; }
}
public class Attributes
{
[JsonProperty("capabilities_tags")] public List<string> CapabilitiesTags { get; set; }
[JsonProperty("creation_date")] public int CreationDate { get; set; }
[JsonProperty("crowdsourced_ids_results")]
public List<CrowdsourcedIdsResult> CrowdsourcedIdsResults { get; set; }
[JsonProperty("crowdsourced_ids_stats")]
public CrowdsourcedIdsStats CrowdsourcedIdsStats { get; set; }
[JsonProperty("crowdsourced_yara_results")]
public List<CrowdsourcedYaraResult> CrowdsourcedYaraResults { get; set; }
[JsonProperty("downloadable")] public bool Downloadable { get; set; }
[JsonProperty("first_submission_date")]
public int FirstSubmissionDate { get; set; }
[JsonProperty("last_analysis_date")] public int LastAnalysisDate { get; set; }
[JsonProperty("last_analysis_results")]
public LastAnalysisResults LastAnalysisResults { get; set; }
[JsonProperty("last_analysis_stats")] public LastAnalysisStats LastAnalysisStats { get; set; }
[JsonProperty("last_modification_date")]
public int LastModificationDate { get; set; }
[JsonProperty("last_submission_date")] public int LastSubmissionDate { get; set; }
[JsonProperty("md5")] public string Md5 { get; set; }
[JsonProperty("meaningful_name")] public string MeaningfulName { get; set; }
[JsonProperty("names")] public List<string> Names { get; set; }
[JsonProperty("reputation")] public int Reputation { get; set; }
[JsonProperty("sandbox_verdicts")] public SandboxVerdicts SandboxVerdicts { get; set; }
[JsonProperty("sha1")] public string Sha1 { get; set; }
[JsonProperty("sha256")] public string Sha256 { get; set; }
[JsonProperty("sigma_analysis_stats")] public SigmaAnalysisStats SigmaAnalysisStats { get; set; }
[JsonProperty("sigma_analysis_summary")]
public SigmaAnalysisSummary SigmaAnalysisSummary { get; set; }
[JsonProperty("size")] public int Size { get; set; }
[JsonProperty("tags")] public List<string> Tags { get; set; }
[JsonProperty("times_submitted")] public int TimesSubmitted { get; set; }
[JsonProperty("total_votes")] public TotalVotes TotalVotes { get; set; }
[JsonProperty("type_description")] public string TypeDescription { get; set; }
[JsonProperty("type_tag")] public string TypeTag { get; set; }
[JsonProperty("unique_sources")] public int UniqueSources { get; set; }
[JsonProperty("vhash")] public string Vhash { get; set; }
}
public class Links
{
[JsonProperty("self")] public string Self { get; set; }
}
public class File
{
[JsonProperty("attributes")] public Attributes Attributes { get; set; }
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("links")] public Links Links { get; set; }
[JsonProperty("type")] public string Type { get; set; }
}
public class Error
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
// ReSharper disable once InconsistentNaming
public class VTResponse<T>
{
[JsonProperty("data")]
public T Data { get; set; }
[JsonProperty("error")]
public Error Error { get; set; }
}
public class VTException : Exception
{
public VTException(Error error)
{
Error = error;
}
protected VTException(SerializationInfo info, StreamingContext context, Error error) : base(info, context)
{
Error = error;
}
public VTException(string? message, Error error) : base(message)
{
Error = error;
}
public VTException(string? message, Exception? innerException, Error error) : base(message, innerException)
{
Error = error;
}
public Error Error { get; }
}

View File

@ -0,0 +1,48 @@
using System.Text;
using Newtonsoft.Json;
using VoidCat.Model;
namespace VoidCat.Services.VirusScanner.VirusTotal;
public class VirusTotalClient
{
private readonly HttpClient _client;
public VirusTotalClient(IHttpClientFactory clientFactory, VirusTotalConfig config)
{
_client = clientFactory.CreateClient();
_client.BaseAddress = new Uri("https://www.virustotal.com/");
_client.DefaultRequestHeaders.Add("x-apikey", config.ApiKey);
_client.DefaultRequestHeaders.Add("accept", "application/json");
}
public async Task<File?> GetReport(string id)
{
return await SendRequest<File>(HttpMethod.Get, $"/api/v3/files/{id}");
}
private Task<TResponse> SendRequest<TResponse>(HttpMethod method, string path)
{
return SendRequest<TResponse, object>(method, path);
}
private async Task<TResponse> SendRequest<TResponse, TRequest>(HttpMethod method, string path, TRequest? body = null)
where TRequest : class
{
var req = new HttpRequestMessage(method, path);
if (body != default)
{
var json = JsonConvert.SerializeObject(body);
req.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(json));
}
var rsp = await _client.SendAsync(req);
var rspBody = await rsp.Content.ReadAsStringAsync();
var vtResponse = JsonConvert.DeserializeObject<VTResponse<TResponse>>(rspBody);
if (vtResponse == default) throw new Exception("Failed?");
if (vtResponse.Error != default) throw new VTException(vtResponse.Error);
return vtResponse.Data;
}
}

View File

@ -0,0 +1,38 @@
using System.Security.Cryptography;
using VoidCat.Model;
using VoidCat.Services.Abstractions;
namespace VoidCat.Services.VirusScanner.VirusTotal;
public class VirusTotalScanner : IVirusScanner
{
private readonly ILogger<VirusTotalScanner> _logger;
private readonly VirusTotalClient _client;
private readonly IFileStore _fileStore;
public VirusTotalScanner(ILogger<VirusTotalScanner> logger, VirusTotalClient client, IFileStore fileStore)
{
_logger = logger;
_client = client;
_fileStore = fileStore;
}
public async ValueTask<VirusScanResult> ScanFile(Guid id, CancellationToken cts)
{
await using var fs = await _fileStore.Open(new(id, Enumerable.Empty<RangeRequest>()), cts);
// hash file and check on VT
var hash = await SHA256.Create().ComputeHashAsync(fs, cts);
var report = await _client.GetReport(hash.ToHex());
if (report != default)
{
return new()
{
IsVirus = report.Attributes.Reputation == 0
};
}
throw new InvalidOperationException();
}
}