/// <summary> /// Sends the specified bytes to the ClamAv server to be scanned for viruses /// </summary> /// <param name="fileBytes">The file bytes to scanned</param> /// <returns>True if a virus is detected false if file is clean</returns> public async Task <bool> FileContainsVirus(byte[] fileBytes) { var clam = new ClamClient(_config[ConfigParameters.ClamAvServerUrl]); ClamScanResult scanResult = await clam.SendAndScanFileAsync(fileBytes); return(scanResult.Result == ClamScanResults.VirusDetected); }
private ScanResult MapScanResult(ClamScanResult clamAvScanResult) { var status = clamAvScanResult.Result; var result = new ScanResult(); switch (clamAvScanResult.Result) { case ClamScanResults.Unknown: result.Message = "Could Not Scan File"; result.IsVirusFree = false; break; case ClamScanResults.Clean: result.Message = "No Virus File"; result.IsVirusFree = true; break; case ClamScanResults.VirusDetected: result.Message = "Virus Found " + clamAvScanResult.InfectedFiles.FirstOrDefault().VirusName; result.IsVirusFree = false; break; case ClamScanResults.Error: result.Message = string.Format("Virus Scan Error! {0}", clamAvScanResult.RawResult); result.IsVirusFree = false; break; default: break; } return(result); }
public static async Task <IActionResult> RunAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req) { IActionResult _ActionResult = new BadRequestObjectResult(INVALID_REQUEST_MESSAGE);; ScanRequest _RequestModel = await GetModelFromRequestBodyAsync(req.Body); if (!string.IsNullOrEmpty(_RequestModel.Base64EncodedBytes)) { _ActionResult = new UnprocessableEntityResult(); ClamScanResult _Result = await new FileScanManager(new ClamClient(Environment.GetEnvironmentVariable("CLAM_AV_SERVER"))).Scan(_RequestModel.Base64EncodedBytes); if (_Result != null) { ScanResponse _Respone = new ScanResponse() { Result = Enum.GetName(typeof(ClamScanResults), _Result.Result), Threat = _Result.InfectedFiles?.First().VirusName ?? string.Empty }; _ActionResult = new JsonResult(_Respone); } } return(_ActionResult); }
/// <summary> /// helper method to map scan results /// </summary> private ScanResult MapScanResult(ClamScanResult scanresult) { var result = new ScanResult(); switch (scanresult.Result) { case ClamScanResults.Unknown: result.Message = "Could not scan file"; result.IsVirusFree = false; break; case ClamScanResults.Clean: result.Message = "No Virus found"; result.IsVirusFree = true; break; case ClamScanResults.VirusDetected: result.Message = "Virus found: " + scanresult.InfectedFiles.First().VirusName; result.IsVirusFree = false; break; case ClamScanResults.Error: result.Message = string.Format("VIRUS SCAN ERROR! {0}", scanresult.RawResult); result.IsVirusFree = false; break; } return(result); }
public void VirusDetected_Response() { var result = new ClamScanResult(@"\\?\C:\test.txt: Eicar-Test-Signature FOUND"); Assert.Equal(ClamScanResults.VirusDetected, result.Result); Assert.Single(result.InfectedFiles); Assert.Equal(@"\\?\C:\test.txt", result.InfectedFiles[0].FileName); Assert.Equal(" Eicar-Test-Signature", result.InfectedFiles[0].VirusName); }
static void Main(string[] args) { Console.WriteLine("Hello World!"); ClamClient client = new ClamClient(SERVER_URL, SERVER_PORT); bool ping = Task.Run(() => client.PingAsync()).Result; Console.WriteLine($"Ping Successful? {ping}"); ClamScanResult result = Task.Run(() => client.ScanFileOnServerAsync(Path.Combine(Environment.CurrentDirectory, fileName))).Result; Console.WriteLine($"Scan Successful? {result.RawResult}"); }
public void Before_Tests() { Assert.Equal( "test:test1", ClamScanResult.before("test:test1:test2") ); Assert.Equal( "", ClamScanResult.before("test") ); Assert.Equal( "test", ClamScanResult.before("test:test1") ); }
public void After_Tests() { //current released behavior to have initial space //(probably a bug) Assert.Equal( " test1", ClamScanResult.after("test test1") ); Assert.Equal( " test2", ClamScanResult.after("test test1 test2") ); Assert.Equal( "", ClamScanResult.after("test") ); }
private async Task <bool> PerformClamAVScan() { var isInfected = false; var clamAVServiceURI = Environment.GetEnvironmentVariable("ClamAVServiceURI"); var clam = new ClamClient(clamAVServiceURI, 3310); ClamScanResult scanResult = null; var timeTaken = await TimedExecutionAsync(async() => { scanResult = await clam.SendAndScanFileAsync(blobStream.ReadAllBytes()); }); logger.LogInformation($"Time taken to perform virus scan {timeTaken} ms"); switch (scanResult.Result) { case ClamScanResults.Clean: logger.LogInformation("The file is clean!"); break; case ClamScanResults.VirusDetected: logger.LogInformation("Virus Found!"); logger.LogInformation("Virus name: {0}", scanResult.InfectedFiles.First().VirusName); isInfected = true; break; case ClamScanResults.Error: logger.LogInformation("Error scanning file: {0}", scanResult.RawResult); break; } return(isInfected); }
public void Non_Matching() { var result = new ClamScanResult(Guid.NewGuid().ToString()); Assert.Equal(ClamScanResults.Unknown, result.Result); }
public void Error_Response() { var result = new ClamScanResult("error"); Assert.Equal(ClamScanResults.Error, result.Result); }
public void OK_Response() { var result = new ClamScanResult(@"C:\test.txt: OK"); Assert.Equal(ClamScanResults.Clean, result.Result); }
public ScanResults(Guid fileGUID, string error) { ScanResult = new ClamScanResult(error); }
public async Task <IActionResult> Upload([FromForm] UploadFileViewModel uploadFile) { try { if (_config.UploadConfig.UploadEnabled) { long maxUploadSize = _config.UploadConfig.MaxUploadSize; if (User.Identity.IsAuthenticated) { maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); if (userInfo.AccountType == AccountType.Premium) { maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; } } else { // Non-logged in users are defaulted to 1 day expiration uploadFile.options.ExpirationUnit = ExpirationUnit.Days; uploadFile.options.ExpirationLength = 1; } if (uploadFile.file.Length <= maxUploadSize) { // convert file to bytes long contentLength = uploadFile.file.Length; // Scan the file to detect a virus if (_config.UploadConfig.VirusScanEnable) { using (Stream fs = uploadFile.file.OpenReadStream()) { ClamClient clam = new ClamClient(_config.UploadConfig.ClamServer, _config.UploadConfig.ClamPort); clam.MaxStreamSize = maxUploadSize; ClamScanResult scanResult = await clam.SendAndScanFileAsync(fs); switch (scanResult.Result) { case ClamScanResults.Clean: break; case ClamScanResults.VirusDetected: return(Json(new { error = new { message = string.Format("Virus Detected: {0}. As per our <a href=\"{1}\">Terms of Service</a>, Viruses are not permited.", scanResult.InfectedFiles.First().VirusName, Url.SubRouteUrl("tos", "TOS.Index")) } })); case ClamScanResults.Error: return(Json(new { error = new { message = string.Format("Error scanning the file upload for viruses. {0}", scanResult.RawResult) } })); case ClamScanResults.Unknown: return(Json(new { error = new { message = string.Format("Unknown result while scanning the file upload for viruses. {0}", scanResult.RawResult) } })); } } } // Check content type restrictions (Only for encrypting server side if (!uploadFile.options.Encrypt) { if (_config.UploadConfig.RestrictedContentTypes.Contains(uploadFile.fileType) || _config.UploadConfig.RestrictedExtensions.Contains(uploadFile.fileExt)) { return(Json(new { error = new { message = "File Type Not Allowed" } })); } } using (Stream fs = uploadFile.file.OpenReadStream()) { Models.Upload upload = UploadHelper.SaveFile(_dbContext, _config, fs, uploadFile.fileType, contentLength, !uploadFile.options.Encrypt, uploadFile.options.ExpirationUnit, uploadFile.options.ExpirationLength, uploadFile.fileExt, uploadFile.iv, null, uploadFile.keySize, uploadFile.blockSize); if (upload != null) { if (User.Identity.IsAuthenticated) { Users.Models.User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { upload.UserId = user.UserId; _dbContext.Entry(upload).State = EntityState.Modified; _dbContext.SaveChanges(); } } return(Json(new { result = new { name = upload.Url, url = Url.SubRouteUrl("u", "Upload.Download", new { file = upload.Url }), contentType = upload.ContentType, contentLength = StringHelper.GetBytesReadable(upload.ContentLength), deleteUrl = Url.SubRouteUrl("u", "Upload.DeleteByKey", new { file = upload.Url, key = upload.DeleteKey }), expirationUnit = uploadFile.options.ExpirationUnit.ToString(), expirationLength = uploadFile.options.ExpirationLength } })); } } return(Json(new { error = new { message = "Unable to upload file" } })); } else { return(Json(new { error = new { message = "File Too Large" } })); } } return(Json(new { error = new { message = "Uploads are disabled" } })); } catch (Exception ex) { return(Json(new { error = new { message = "Exception while uploading file: " + ex.GetFullMessage(true) } })); } }
public async Task <IActionResult> Upload(UploadAPIv1Model model) { try { if (_config.UploadConfig.UploadEnabled) { if (model.file != null) { long maxUploadSize = _config.UploadConfig.MaxUploadSize; if (User.Identity.IsAuthenticated) { maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); if (userInfo.AccountType == AccountType.Premium) { maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; } } else { // Non-logged in users are defaulted to 1 day expiration model.expirationUnit = ExpirationUnit.Days; model.expirationLength = 1; } if (model.file.Length <= maxUploadSize) { // convert file to bytes string fileExt = FileHelper.GetFileExtension(model.file.FileName); long contentLength = model.file.Length; // Scan the file to detect a virus if (_config.UploadConfig.VirusScanEnable) { ClamClient clam = new ClamClient(_config.UploadConfig.ClamServer, _config.UploadConfig.ClamPort); clam.MaxStreamSize = maxUploadSize; ClamScanResult scanResult = await clam.SendAndScanFileAsync(model.file.OpenReadStream()); switch (scanResult.Result) { case ClamScanResults.Clean: break; case ClamScanResults.VirusDetected: return(Json(new { error = new { message = string.Format("Virus Detected: {0}. As per our <a href=\"{1}\">Terms of Service</a>, Viruses are not permited.", scanResult.InfectedFiles.First().VirusName, Url.SubRouteUrl("tos", "TOS.Index")) } })); case ClamScanResults.Error: break; case ClamScanResults.Unknown: break; } } // Need to grab the contentType if it's empty if (string.IsNullOrEmpty(model.contentType)) { model.contentType = model.file.ContentType; if (string.IsNullOrEmpty(model.contentType)) { using (Stream fileStream = model.file.OpenReadStream()) { fileStream.Seek(0, SeekOrigin.Begin); FileType fileType = fileStream.GetFileType(); if (fileType != null) { model.contentType = fileType.Mime; } if (string.IsNullOrEmpty(model.contentType)) { model.contentType = "application/octet-stream"; } } } } // Check content type restrictions (Only for encrypting server side if (model.encrypt || !string.IsNullOrEmpty(model.key)) { if (_config.UploadConfig.RestrictedContentTypes.Contains(model.contentType) || _config.UploadConfig.RestrictedExtensions.Contains(fileExt)) { return(Json(new { error = new { message = "File Type Not Allowed" } })); } } // Initialize the key size and block size if empty if (model.keySize <= 0) { model.keySize = _config.UploadConfig.KeySize; } if (model.blockSize <= 0) { model.blockSize = _config.UploadConfig.BlockSize; } // Save the file data Upload.Models.Upload upload = UploadHelper.SaveFile(_dbContext, _config, model.file.OpenReadStream(), model.contentType, contentLength, model.encrypt, model.expirationUnit, model.expirationLength, fileExt, model.iv, model.key, model.keySize, model.blockSize); if (upload != null) { string fileKey = upload.Key; // Associate this with the user if they provided an auth key if (User.Identity.IsAuthenticated) { User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name); if (foundUser != null) { upload.UserId = foundUser.UserId; _dbContext.Entry(upload).State = EntityState.Modified; _dbContext.SaveChanges(); } } // Generate delete key only if asked to if (!model.genDeletionKey) { upload.DeleteKey = string.Empty; _dbContext.Entry(upload).State = EntityState.Modified; _dbContext.SaveChanges(); } // remove the key if we don't want to save it if (!model.saveKey) { upload.Key = null; _dbContext.Entry(upload).State = EntityState.Modified; _dbContext.SaveChanges(); } // Pull all the information together string fullUrl = Url.SubRouteUrl("u", "Upload.Download", new { file = upload.Url }); var returnData = new { url = (model.saveKey || string.IsNullOrEmpty(fileKey)) ? fullUrl : fullUrl + "#" + fileKey, fileName = upload.Url, contentType = upload.ContentType, contentLength = upload.ContentLength, key = fileKey, keySize = upload.KeySize, iv = upload.IV, blockSize = upload.BlockSize, maxDownloads = upload.MaxDownloads, expirationDate = upload.ExpireDate, deletionKey = upload.DeleteKey }; return(Json(new { result = returnData })); } return(Json(new { error = new { message = "Unable to save file" } })); } else { return(Json(new { error = new { message = "File Too Large" } })); } } return(Json(new { error = new { message = "Invalid Upload Request" } })); } return(Json(new { error = new { message = "Uploads are Disabled" } })); } catch (Exception ex) { return(Json(new { error = new { message = "Exception: " + ex.Message } })); } }
public static void ClamScanFileFromRepo(DbFile file) { try { if (Context.ClamdVersion == null) { Failed?.Invoke("clamd is not usable"); return; } if (clam == null) { Failed?.Invoke("clamd is not initalized"); } string repoPath; AlgoEnum algorithm; if (File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".gz"))) { repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".gz"); algorithm = AlgoEnum.GZip; } else if (File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".bz2"))) { repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".bz2"); algorithm = AlgoEnum.BZip2; } else if (File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".lzma"))) { repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".lzma"); algorithm = AlgoEnum.LZMA; } else if (File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".lz"))) { repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(), file.Sha256[1].ToString(), file.Sha256[2].ToString(), file.Sha256[3].ToString(), file.Sha256[4].ToString(), file.Sha256 + ".lz"); algorithm = AlgoEnum.LZip; } else { Failed?.Invoke($"Cannot find file with hash {file.Sha256} in the repository"); return; } ClamScanResult result = null; Stream zStream = null; if (Settings.Current.ClamdIsLocal) { if (algorithm == AlgoEnum.LZMA || algorithm == AlgoEnum.LZip) { string tmpFile = Path.Combine(Settings.Current.TemporaryFolder, Path.GetTempFileName()); FileStream outFs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write); FileStream inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read); if (algorithm == AlgoEnum.LZMA) { byte[] properties = new byte[5]; inFs.Read(properties, 0, 5); inFs.Seek(8, SeekOrigin.Current); zStream = new LzmaStream(properties, inFs, inFs.Length - 13, file.Length); } else { zStream = new LZipStream(inFs, CompressionMode.Decompress); } UpdateProgress?.Invoke("Uncompressing file...", null, 0, 0); #if DEBUG stopwatch.Restart(); #endif zStream.CopyTo(outFs); zStream.Close(); outFs.Close(); #if DEBUG stopwatch.Stop(); Console.WriteLine("Core.ClamScanFileFromRepo({0}): Uncompressing took {1} seconds", file, stopwatch.Elapsed.TotalSeconds); #endif UpdateProgress?.Invoke("Requesting local scan to clamd server...", null, 0, 0); #if DEBUG stopwatch.Restart(); #endif Task.Run(async() => { result = await clam.ScanFileOnServerMultithreadedAsync(tmpFile); }) .Wait(); #if DEBUG stopwatch.Stop(); Console.WriteLine("Core.ClamScanFileFromRepo({0}): Clamd took {1} seconds to scan", file, stopwatch.Elapsed.TotalSeconds); #endif File.Delete(tmpFile); } else { UpdateProgress?.Invoke("Requesting local scan to clamd server...", null, 0, 0); #if DEBUG stopwatch.Restart(); #endif Task.Run(async() => { result = await clam.ScanFileOnServerMultithreadedAsync(repoPath); }) .Wait(); #if DEBUG stopwatch.Stop(); Console.WriteLine("Core.ClamScanFileFromRepo({0}): Clamd took {1} seconds to scan", file, stopwatch.Elapsed.TotalSeconds); #endif } } else { FileStream inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read); switch (algorithm) { case AlgoEnum.GZip: zStream = new GZipStream(inFs, CompressionMode.Decompress); break; case AlgoEnum.BZip2: zStream = new BZip2Stream(inFs, CompressionMode.Decompress); break; case AlgoEnum.LZMA: byte[] properties = new byte[5]; inFs.Read(properties, 0, 5); inFs.Seek(8, SeekOrigin.Current); zStream = new LzmaStream(properties, inFs, inFs.Length - 13, file.Length); break; case AlgoEnum.LZip: zStream = new LZipStream(inFs, CompressionMode.Decompress); break; } UpdateProgress?.Invoke("Uploading file to clamd server...", null, 0, 0); #if DEBUG stopwatch.Restart(); #endif Task.Run(async() => { result = await clam.SendAndScanFileAsync(zStream); }).Wait(); #if DEBUG stopwatch.Stop(); Console.WriteLine("Core.ClamScanFileFromRepo({0}): Clamd took {1} seconds to scan", file, stopwatch.Elapsed.TotalSeconds); #endif zStream.Close(); } if (result.InfectedFiles != null && result.InfectedFiles.Count > 0) { file.HasVirus = true; file.Virus = result.InfectedFiles[0].VirusName; } else if (file.HasVirus == null) { // If no scan has been done, mark as false. // If a positive has already existed don't overwrite it. file.HasVirus = false; file.Virus = null; } file.ClamTime = DateTime.UtcNow; dbCore.DbOps.UpdateFile(file); ScanFinished?.Invoke(file); } catch (ThreadAbortException) { } catch (Exception ex) { Failed?.Invoke($"Exception {ex.Message} when calling clamd"); #if DEBUG Console.WriteLine("Exception {0}\n{1}", ex.Message, ex.InnerException); #endif } }
private static async Task <bool> ScanUpload(Config config, TeknikEntities db, Upload upload, int totalCount, int currentCount) { bool virusDetected = false; string subDir = upload.FileName[0].ToString(); string filePath = Path.Combine(config.UploadConfig.UploadDirectory, subDir, upload.FileName); if (File.Exists(filePath)) { // If the IV is set, and Key is set, then scan it if (!string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV)) { byte[] keyBytes = Encoding.UTF8.GetBytes(upload.Key); byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV); long maxUploadSize = config.UploadConfig.MaxUploadSize; if (upload.User != null) { maxUploadSize = config.UploadConfig.MaxUploadSizeBasic; IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(config, upload.User.Username); if (userInfo.AccountType == AccountType.Premium) { maxUploadSize = config.UploadConfig.MaxUploadSizePremium; } } using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (AesCounterStream aesStream = new AesCounterStream(fs, false, keyBytes, ivBytes)) { ClamClient clam = new ClamClient(config.UploadConfig.ClamServer, config.UploadConfig.ClamPort); clam.MaxStreamSize = maxUploadSize; ClamScanResult scanResult = await clam.SendAndScanFileAsync(fs); switch (scanResult.Result) { case ClamScanResults.Clean: string cleanMsg = string.Format("[{0}] Clean Scan: {1}/{2} Scanned | {3} - {4}", DateTime.Now, currentCount, totalCount, upload.Url, upload.FileName); Output(cleanMsg); break; case ClamScanResults.VirusDetected: string msg = string.Format("[{0}] Virus Detected: {1} - {2} - {3}", DateTime.Now, upload.Url, upload.FileName, scanResult.InfectedFiles.First().VirusName); Output(msg); lock (scanStatsLock) { virusDetected = true; File.AppendAllLines(virusFile, new List <string> { msg }); } lock (dbLock) { string urlName = upload.Url; // Delete from the DB db.Uploads.Remove(upload); // Delete the File if (File.Exists(filePath)) { File.Delete(filePath); } // Add to transparency report if any were found Takedown report = new Takedown(); report.Requester = TAKEDOWN_REPORTER; report.RequesterContact = config.SupportEmail; report.DateRequested = DateTime.Now; report.Reason = "Malware Found"; report.ActionTaken = string.Format("Upload removed: {0}", urlName); report.DateActionTaken = DateTime.Now; db.Takedowns.Add(report); // Save Changes db.SaveChanges(); } break; case ClamScanResults.Error: string errorMsg = string.Format("[{0}] Scan Error: {1}", DateTime.Now, scanResult.RawResult); File.AppendAllLines(errorFile, new List <string> { errorMsg }); Output(errorMsg); break; case ClamScanResults.Unknown: string unkMsg = string.Format("[{0}] Unknown Scan Result: {1}", DateTime.Now, scanResult.RawResult); File.AppendAllLines(errorFile, new List <string> { unkMsg }); Output(unkMsg); break; } } } } return(virusDetected); }
public ScanResults(Guid fileGUID, ClamScanResult scanResult) { FileGUID = fileGUID; ScanResult = scanResult; }