Skip to content

Commit

Permalink
Adding tweaks to service code, and adding ESET to docs
Browse files Browse the repository at this point in the history
  • Loading branch information
cccs-kevin committed Jun 14, 2021
1 parent 78a3007 commit be600b4
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 6 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ So far, we have tested with the following:
- https://www.kaspersky.com/scan-engine
- McAfee Web Gateway with ICAP and HTTP turned on: McAfee Web Gateway 9.2.2 build 33635
- https://docs.mcafee.com/bundle/web-gateway-9.2.x-product-guide
- ESET File Security For Linux x64 v8.0.375.0, with "Remote scanning - ICAP" enabled
- https://help.eset.com/efs/8/en-US/

In theory, any antivirus product will work with this service as long as it is configured for ICAP or HTTP requests.

Expand All @@ -17,7 +19,7 @@ failures so that we can adapt the service!
### Things you need:
- The antivirus product must be setup and ready to accept files (via HTTP or ICAP). You are in charge of setting up the
antivirus product, yay responsibility!
- `product`: A unique name for each antivirus product (Kaspersky, McAfee, etc.)
- `product`: A unique name for each antivirus product (Kaspersky, McAfee, ESET, etc.)
- `ip` & `port`: The IP address and port of at least one host that is serving each antivirus product.
- `update_period`: The period/interval (in minutes) in which the antivirus product host polls for updates.

Expand Down Expand Up @@ -76,12 +78,23 @@ av_config:
virus_name_header: "X-Virus-Name"
scan_endpoint: "filescanner"
update_period: 240
- product: "ESET"
hosts:
- ip: "<ip>"
port: 1344
method: "icap"
icap_scan_details:
no_version: true
virus_name_header: "X-Infection-Found: Type=0; Resolution=0; Threat"
update_period: 240
```

### Explanations of ICAP and HTTP YAML details:
#### ICAP
- `virus_name_header`: The name of the header of the line in the results that contains the antivirus hit name. Example of a line in the results (either in the response headers or body): `X-Virus-ID: <some-signature-here>`. The `virus_name_header` would be `X-Virus-ID`.
- `scan_endpoint`: The URI endpoint at which the service is listening for file contents to be submitted or OPTIONS to be queried.
- `no_version`: A boolean indicating if a product version will be returned if you query OPTIONS.

#### HTTP
- `virus_name_header`: The name of the header of the line in the results that contains the antivirus hit name. Example of a line in the results (either in the response headers or body): `X-Virus-ID: <some-signature-here>`. The `virus_name_header` would be `X-Virus-ID`.
Expand Down
10 changes: 8 additions & 2 deletions antivirus.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
DEFAULT_WAIT_TIME_BETWEEN_RETRIES = 60 # in seconds
DEFAULT_WAIT_TIME_BETWEEN_COMPLETION_CHECKS = 500 # in milliseconds
VERSION_REGEX = r"(?<=\().*?(?=\))" # Grabs a substring found between parentheses
CHARS_TO_STRIP = [";", ":", "="]

# Specific signature names
REVISED_SIG_SCORE_MAP = {}
Expand Down Expand Up @@ -114,15 +115,17 @@ class ICAPScanDetails:
"""
This class contains details regarding scanning and parsing a file via ICAP
"""
def __init__(self, virus_name_header: str = "X-Virus-ID", scan_endpoint: str = "") -> None:
def __init__(self, virus_name_header: str = "X-Virus-ID", scan_endpoint: str = "", no_version: bool = False) -> None:
"""
This method initializes the ICAPScanDetails class
@param virus_name_header: The name of the header of the line in the results that contains the antivirus hit name
@param scan_endpoint: The URI endpoint at which the service is listening for file contents to be submitted or OPTIONS to be queried.
@param no_version: A boolean indicating if a product version will be returned if you query OPTIONS.
@return: None
"""
self.virus_name_header = virus_name_header
self.scan_endpoint = scan_endpoint
self.no_version = no_version

def __eq__(self, other):
"""
Expand Down Expand Up @@ -195,6 +198,9 @@ def __init__(self, av_name: str, av_version: Optional[str], virus_name: str, eng
@param heur_id: Essentially an integer flag that indicates if the scanned file is "infected" or "suspicious"
@return: None
"""
for char_to_strip in CHARS_TO_STRIP:
virus_name = virus_name.replace(char_to_strip, "")

title = f"{av_name} identified the file as {virus_name}"
json_body = dict(
av_name=av_name,
Expand Down Expand Up @@ -338,7 +344,7 @@ def _scan_file(self, host: AntiVirusHost, file_name: str, file_contents: bytes)\
version: Optional[str] = None
try:
if host.method == ICAP_METHOD:
version = host.client.options_respmod()
version = host.client.options_respmod() if not host.icap_scan_details.no_version else None
results = host.client.scan_data(file_contents, file_name)
elif host.method == HTTP_METHOD:
base_url = f"{HTTP_METHOD}://{host.ip}:{host.port}"
Expand Down
14 changes: 11 additions & 3 deletions test/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,13 @@ def test_init(antivirushost_class):
assert type(avhost_icap_with_no_details.icap_scan_details) == ICAPScanDetails
assert avhost_icap_with_no_details.icap_scan_details.virus_name_header == "X-Virus-ID"
assert avhost_icap_with_no_details.icap_scan_details.scan_endpoint == ""
assert avhost_icap_with_no_details.icap_scan_details.no_version is False
assert avhost_icap_with_no_details.http_scan_details is None
assert avhost_icap_with_no_details.update_period == 100
assert type(avhost_icap_with_no_details.client) == IcapClient
assert avhost_icap_with_no_details.sleeping is False

avhost_icap_with_details = antivirushost_class("blah", "blah", 8008, "icap", 100, icap_scan_details={"virus_name_header": "blah", "scan_endpoint": "blah"})
avhost_icap_with_details = antivirushost_class("blah", "blah", 8008, "icap", 100, icap_scan_details={"virus_name_header": "blah", "scan_endpoint": "blah", "no_version": True})
assert avhost_icap_with_details.group == "blah"
assert avhost_icap_with_details.ip == "blah"
assert avhost_icap_with_details.port == 8008
Expand All @@ -177,6 +178,7 @@ def test_init(antivirushost_class):
assert type(avhost_icap_with_details.icap_scan_details) == ICAPScanDetails
assert avhost_icap_with_details.icap_scan_details.virus_name_header == "blah"
assert avhost_icap_with_details.icap_scan_details.scan_endpoint == "blah"
assert avhost_icap_with_details.icap_scan_details.no_version is True
assert avhost_icap_with_details.http_scan_details is None
assert avhost_icap_with_details.update_period == 100
assert type(avhost_icap_with_details.client) == IcapClient
Expand Down Expand Up @@ -249,17 +251,19 @@ def test_init():
no_details = ICAPScanDetails()
assert no_details.virus_name_header == "X-Virus-ID"
assert no_details.scan_endpoint == ""
assert no_details.no_version is False

with_details = ICAPScanDetails("blah", "blah")
with_details = ICAPScanDetails("blah", "blah", True)
assert with_details.virus_name_header == "blah"
assert with_details.scan_endpoint == "blah"
assert with_details.no_version is True

@staticmethod
def test_eq():
from antivirus import ICAPScanDetails
icap1 = ICAPScanDetails()
icap2 = ICAPScanDetails()
icap3 = ICAPScanDetails("blah", "blah")
icap3 = ICAPScanDetails("blah", "blah", True)
assert icap1 == icap2
assert icap1 != icap3

Expand Down Expand Up @@ -433,6 +437,9 @@ def test_scan_file(antivirus_class_instance, antivirushost_class, dummy_requests
av_host_icap = antivirushost_class("blah", "blah", 1234, "icap", 100)
assert antivirus_class_instance._scan_file(av_host_icap, "blah", b"blah") == ("blah", "blah", av_host_icap)

av_host_icap = antivirushost_class("blah", "blah", 1234, "icap", 100, icap_scan_details={"no_version": True})
assert antivirus_class_instance._scan_file(av_host_icap, "blah", b"blah") == ("blah", None, av_host_icap)

mocker.patch.object(Session, "get", return_value=dummy_requests_class_instance("blah"))
mocker.patch.object(Session, "post", return_value=dummy_requests_class_instance("blah"))
av_host_http = antivirushost_class("blah", "blah", 1234, "http", 100)
Expand All @@ -442,6 +449,7 @@ def test_scan_file(antivirus_class_instance, antivirushost_class, dummy_requests
assert antivirus_class_instance._scan_file(av_host_http, "blah", b"blah") == ('{"Via": "(blah)"}', "blah", av_host_http)

with mocker.patch.object(IcapClient, "scan_data", side_effect=timeout):
av_host_icap = antivirushost_class("blah", "blah", 1234, "icap", 100)
assert av_host_icap.sleeping is False
antivirus_class_instance.retry_period = 2
assert antivirus_class_instance._scan_file(av_host_icap, "blah", b"blah") == (None, "blah", av_host_icap)
Expand Down

0 comments on commit be600b4

Please sign in to comment.