Antimalware in a Dockerized backend (Spring-boot and ClamAV)

aminbe
2 min readApr 20, 2021

--

Context

We would like to integrate an antivirus into our application, which will scan the uploaded files and decide whether or not to save them in our file system.

Overview

We have a springboot application dockerized and deployed with Docker Swarm.

The architecture we adopt is the following: the antivirus will be hosted in a container and we will request it as a web service.

Docker layer

We recommend the use of ClamAV, a proven antivirus.

Therefore, we use an open source docker image to mount our container hosting ClamAV, the chosen image is mailu/clamav. Our docker-compose file looks like this :

version: "3.1"services:
api:
hostname: api
container_name: api
build: ./api/.
ports:
- "9999:9999"
depends_on:
- scan
scan:
hostname: scan
container_name: scan
image: mailu/clamav:1.7
ports:
- "3310:3310"
restart: always

Spring-boot layer

We may use this ClamAV Client, we could also integrate its source code in our project.

In this tutorial, we just use this dependency in our pom.xml file.

pom.xml dependency

  <!-- A clamAV client implementation -->
<dependency>
<groupId>fi.solita.clamav</groupId>
<artifactId>clamav-client</artifactId>
<version>1.0.1</version>
</dependency>

A fileScan service

@Service
public class FileScanServiceClam implements FileScanService {
private final ClamAVClient clamAVClient; public FileScanServiceClam(){
this.clamAVClient = new ClamAVClient("scan", 3310);
}
public List<FileScanResponse> scanFiles(MultipartFile[] files) {
return Arrays.stream(files).map(multipartFile -> {
FileScanResponse fileScanResponse = new FileScanResponse();
long startTime = System.currentTimeMillis();
fileScanResponse.setUploadTime(startTime);
try {
byte[] response = this.clamAVClient.scan(multipartFile.getInputStream());
boolean status = ClamAVClient.isCleanReply(response);
fileScanResponse.setDetected(!status);
System.out.println("File Scanned = " + multipartFile.getOriginalFilename() + ", Clam AV Response = " + status);
} catch(Exception ex) {
System.out.println("Exception occurred while scanning using clam av = " + ex.getMessage());
fileScanResponse.setErrorMessage(ex.getMessage());
}
fileScanResponse.setFileName(multipartFile.getOriginalFilename());
fileScanResponse.setSize(multipartFile.getSize());
fileScanResponse.setScanTimeInMilliSec(System.currentTimeMillis() - startTime);
return fileScanResponse;
}).collect(Collectors.toList());
}
}

Service integration to a controller

@PostMapping("/upload")
public ScanResponse<List<FileScanResponse>> uploadFiles(@RequestParam("files") MultipartFile files) {
return new ScanResponse<>(fileScanServiceClam.scanFiles(new MultipartFile[]{files}));
}

Carrying out a functional test

It is possible to create a test file containing the following string : corrupted_file.txt

X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

PS: Don’t forget to add this file to the antivirus exceptions.

The test curl command :

$ curl -X POST <http://localhost:9999/entrypoint/upload> -H "accept: */*" -H "Content-Type: multipart/form-data" -F "files=@corrupted_file.txt;type=application/json"{"data":[{"fileName":"corrupted_file.txt","detected":true,"size":68,"scanTimeInMilliSec":3,"errorMessage":null,"hash":null,"uploadTime":1600246731991}],"metaData":null}

⇒ Detected is equal to true

Extra-link

An implementation : https://github.com/AmineBenaddiGitHub/rt-spbt-nginx-docker

--

--

aminbe
aminbe

Responses (1)