mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-03-27 00:21:42 +01:00
Add virus total scanner
This commit is contained in:
parent
cff3b70403
commit
8340916c15
@ -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; }
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ if (useRedis)
|
||||
services.AddSingleton(cx.GetDatabase());
|
||||
}
|
||||
|
||||
services.AddHttpClient();
|
||||
services.AddCors(opt =>
|
||||
{
|
||||
opt.AddDefaultPolicy(p =>
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
327
VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClasses.cs
Normal file
327
VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClasses.cs
Normal 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; }
|
||||
}
|
48
VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClient.cs
Normal file
48
VoidCat/Services/VirusScanner/VirusTotal/VirusTotalClient.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user