Skip to content

Commit 6ef04f9

Browse files
authored
Merge pull request #495 from target/ScanClamAV
ScanClamAV
2 parents 9b4289e + da67ad8 commit 6ef04f9

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-0
lines changed

build/python/backend/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ RUN mkdir jtr && cd jtr && git init && git remote add origin https://github.com/
139139
chmod -R 777 /jtr && \
140140
chown -R $USER_UID:$USER_UID /jtr
141141

142+
# Install ClamAV
143+
RUN apt-get update -qq && \
144+
apt-get install -qq -y --no-install-recommends \
145+
clamav \
146+
clamav-base \
147+
clamav-daemon \
148+
clamav-freshclam
149+
150+
# Update permissions for relevant fresclam log files
151+
RUN touch /var/log/clamav/freshclam.log
152+
RUN chmod 777 /var/log/clamav/freshclam.log
153+
RUN chown clamav /var/log/clamav/freshclam.log
154+
RUN chown 1001:1001 /var/lib/clamav
155+
142156
# Install Poetry globally and copy project files
143157
RUN python3 -m pip install -U pip setuptools && \
144158
python3 -m pip install poetry && \

configs/python/backend/backend.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ scanners:
6969
- 'application/x-bzip2'
7070
- 'bzip2_file'
7171
priority: 5
72+
'ScanClamav':
73+
- positive:
74+
flavors:
75+
- '*'
76+
priority: 5
7277
# 'ScanCcn':
7378
# - positive:
7479
# flavors:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import shutil
2+
import subprocess
3+
import tempfile
4+
5+
from strelka import strelka
6+
7+
8+
class ScanClamav(strelka.Scanner):
9+
"""
10+
This scanner runs against a given file and returns a ClamAV scan that has a determination if the file is infected
11+
or not based on the ClamAV signature database.
12+
13+
Scanner Type: Collection
14+
15+
Attributes:
16+
None
17+
18+
## Detection Use Cases
19+
!!! info "Detection Use Cases"
20+
- **Scan Determination**
21+
- This scanner provides a inital determination on a file if it is infected or not based on the
22+
the ClamAV signature database.
23+
24+
## Known Limitations
25+
!!! warning "Known Limitations"
26+
- **ClamAV Signature Database**
27+
- This scanner relies on the ClamAV signature database which is not necesarily all-encompassing. Though
28+
the scanner may return a determination, users should be advise that this is not exaustive.
29+
30+
## To Do
31+
!!! question "To Do"
32+
- The ClamAV signature database is currently pulled every scan as a POC. This could be converted to a
33+
signature pull on a cadence, such as every 24 hours.
34+
35+
## References
36+
!!! quote "References"
37+
- [ClamAV Documentation Source](https://docs.clamav.net/Introduction.html)
38+
- [BlogPost on ClamAV Scanner](https://simovits.com/strelka-let-us-build-a-scanner/)
39+
40+
## Contributors
41+
!!! example "Contributors"
42+
- [Sara Kalupa](https://github.com/skalupa)
43+
44+
"""
45+
46+
def scan(self, data, file, options, expire_at):
47+
try:
48+
# Check if ClamAV package is installed
49+
if not shutil.which("clamscan"):
50+
self.flags.append("clamav_not_installed_error")
51+
return
52+
except Exception as e:
53+
self.flags.append(str(e))
54+
return
55+
56+
try:
57+
with tempfile.NamedTemporaryFile(dir="/tmp/", mode="wb") as tmp_data:
58+
tmp_data.write(data)
59+
tmp_data.flush()
60+
tmp_data.seek(0)
61+
62+
# Run freshclam to gret the newest database signatures
63+
stdout, stderr = subprocess.Popen(
64+
["freshclam"],
65+
stdout=subprocess.PIPE,
66+
stderr=subprocess.PIPE,
67+
).communicate(timeout=self.scanner_timeout)
68+
69+
temp_log = tempfile.NamedTemporaryFile(dir="/tmp/", mode="wb")
70+
loglocation = "--log=" + temp_log.name
71+
72+
# Run the actual ClamAV scan and report to local temp log file
73+
process = subprocess.Popen(
74+
["clamscan", "--disable-cache", loglocation, tmp_data.name],
75+
stdout=subprocess.PIPE,
76+
stderr=subprocess.PIPE,
77+
)
78+
stdout, stderr = process.communicate()
79+
80+
with open(temp_log.name, "r") as file:
81+
for line in file:
82+
if ":" in line:
83+
# Attempt to split out scan information
84+
splitline = line.split(":")
85+
self.event[splitline[0]] = splitline[1].strip()
86+
else:
87+
continue
88+
89+
except strelka.ScannerTimeout:
90+
raise
91+
except Exception:
92+
self.flags.append("clamAV_Scan_process_error")
93+
raise
94+
finally:
95+
# Ensure that tempfile gets closed out if there are any issues
96+
tmp_data.close()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from pathlib import Path
2+
from unittest import TestCase, mock
3+
4+
from strelka.scanners.scan_clamav import ScanClamav as ScanUnderTest
5+
from strelka.tests import run_test_scan
6+
7+
8+
def test_scan_clamav(mocker):
9+
"""
10+
Pass: Sample event matches output of scanner.
11+
Failure: Unable to load file or sample event fails to match.
12+
"""
13+
test_scan_event = {
14+
"Data read": "0.51 MB (ratio 1.06",
15+
"Data scanned": "0.54 MB",
16+
"End Date": mock.ANY,
17+
"Engine version": "0.103.12",
18+
"Infected files": "0",
19+
"Known viruses": "8706344",
20+
"Scanned directories": "0",
21+
"Scanned files": "1",
22+
"Start Date": mock.ANY,
23+
"Time": mock.ANY,
24+
"elapsed": mock.ANY,
25+
"flags": [],
26+
}
27+
28+
scanner_event = run_test_scan(
29+
mocker=mocker,
30+
scan_class=ScanUnderTest,
31+
fixture_path=Path(__file__).parent / "fixtures/test.png",
32+
)
33+
34+
TestCase.maxDiff = None
35+
TestCase().assertDictEqual(test_scan_event, scanner_event)

0 commit comments

Comments
 (0)