CVE-2024-37164 (published 2024-06-13T15:15Z)

Computer Vision Annotation Tool (CVAT) is an interactive video and image annotation tool for computer vision. CVAT allows users to supply custom endpoint URLs for cloud storages based on Amazon S3 and Azure Blob Storage. Starting in version 2.1.0 and prior to version 2.14.3, an attacker with a CVAT account can exploit this feature by specifying URLs whose host part is an intranet IP address or an internal domain name. By doing this, the attacker may be able to probe the network that the CVAT backend runs in for HTTP(S) servers. In addition, if there is a web server on this network that is sufficiently API-compatible with an Amazon S3 or Azure Blob Storage endpoint, and either allows anonymous access, or allows authentication with credentials that are known by the attacker, then the attacker may be able to create a cloud storage linked to this server. They may then be able to list files on the server; extract files from the server, if these files are of a type that CVAT supports reading from cloud storage (media data (such as images/videos/archives), importable annotations or datasets, task/project backups); and/or overwrite files on this server with exported annotations/datasets/backups. The exact capabilities of the attacker will depend on how the internal server is configured. Users should upgrade to CVAT 2.14.3 to receive a patch. In this release, the existing SSRF mitigation measures are applied to requests to cloud providers, with access to intranet IP addresses prohibited by default. Some workarounds are also available. One may use network security solutions such as virtual networks or firewalls to prohibit network access from the CVAT backend to unrelated servers on your internal network and/or require authentication for access to internal servers.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/cvat-ai/cvat
commit_details: repo_url: https://github.com/cvat-ai/cvat
author: Roman Donchenko
author_date: 2024-06-13 15:16:54+03:00
msg: Merge pull request from GHSA-q684-4jjh-83g6

S3 storages support user-specified endpoint URLs, and Azure storages support
user-specified connection strings (which can contain endpoint URLs), so they
are susceptible to SSRF. Make S3 and Azure requests go through smokescreen
to fix this.

AFAIK, there is no way to configure a custom URL for Google Cloud storages,
so those aren't vulnerable.

Co-authored-by: Nikita Manovich <nikita@cvat.ai>
repository: repo_url: https://github.com/cvat-ai/cvat
repo_name: cvat-ai/cvat
description: Annotate better with CVAT, the industry-leading data engine for machine learning. Used and trusted by teams at any scale, for data of any scale.
repo_language: TypeScript
owner: cvat-ai
file_changes: filename: 20240610_143947_roman_ssrf_s3.md
change_type: ModificationType.ADD
diff: @@ -0,0 +1,4 @@
+### Security
+
+- Fixed an SSRF vulnerability with custom cloud storage endpoints
+  (<https://github.com/cvat-ai/cvat/security/advisories/GHSA-q684-4jjh-83g6>)

programming_language: Markdown
filename: cloud_provider.py
change_type: ModificationType.MODIFY
diff: @@ -18,6 +18,7 @@ from azure.core.exceptions import HttpResponseError, ResourceExistsError
 from azure.storage.blob import BlobServiceClient, ContainerClient, PublicAccess
 from azure.storage.blob._list_blobs_helper import BlobPrefix
 from boto3.s3.transfer import TransferConfig
+from botocore.client import Config
 from botocore.exceptions import ClientError
 from botocore.handlers import disable_signing
 from datumaro.util import take_by # can be changed to itertools.batched after migration to python3.12
@@ -32,6 +33,7 @@ from rest_framework.exceptions import (NotFound, PermissionDenied,
 from cvat.apps.engine.log import ServerLogManager
 from cvat.apps.engine.models import CloudProviderChoice, CredentialsTypeChoice
 from cvat.apps.engine.utils import get_cpu_number
+from cvat.utils.http import PROXIES_FOR_UNTRUSTED_URLS
 
 class NamedBytesIO(BytesIO):
     @property
@@ -442,7 +444,9 @@ class AWS_S3(_CloudStorage):
                 kwargs[key] = arg_v
 
         session = boto3.Session(**kwargs)
-        self._s3 = session.resource("s3", endpoint_url=endpoint_url)
+        self._s3 = session.resource("s3", endpoint_url=endpoint_url,
+            config=Config(proxies=PROXIES_FOR_UNTRUSTED_URLS or {}),
+        )
 
         # anonymous access
         if not any([access_key_id, secret_key, session_token]):
@@ -644,11 +648,14 @@ class AzureBlobContainer(_CloudStorage):
         super().__init__(prefix=prefix)
         self._account_name = account_name
         if connection_string:
-            self._blob_service_client = BlobServiceClient.from_connection_string(connection_string)
+            self._blob_service_client = BlobServiceClient.from_connection_string(
+                connection_string, proxies=PROXIES_FOR_UNTRUSTED_URLS)
         elif sas_token:
-            self._blob_service_client = BlobServiceClient(account_url=self.account_url, credential=sas_token)
+            self._blob_service_client = BlobServiceClient(
+                account_url=self.account_url, credential=sas_token, proxies=PROXIES_FOR_UNTRUSTED_URLS)
         else:
-            self._blob_service_client = BlobServiceClient(account_url=self.account_url)
+            self._blob_service_client = BlobServiceClient(
+                account_url=self.account_url, proxies=PROXIES_FOR_UNTRUSTED_URLS)
         self._client = self._blob_service_client.get_container_client(container)
 
     @property

programming_language: Python
filename: worker.export.conf
change_type: ModificationType.MODIFY
diff: @@ -25,3 +25,6 @@ environment=VECTOR_EVENT_HANDLER="SynchronousLogstashHandler",CVAT_POSTGRES_APPL
 numprocs=%(ENV_NUMPROCS)s
 process_name=%(program_name)s-%(process_num)d
 autorestart=true
+
+[program:smokescreen]
+command=smokescreen --listen-ip=127.0.0.1 %(ENV_SMOKESCREEN_OPTS)s

programming_language: Shell
filename: allow_minio.sh
change_type: ModificationType.ADD
diff: @@ -0,0 +1,3 @@
+# Same as allow_webhooks_receiver.sh, but for minio.
+minio_ip_addr="$(getent hosts minio | head -1 | awk '{ print $1 }')"
+export SMOKESCREEN_OPTS="$SMOKESCREEN_OPTS --allow-address=\"$minio_ip_addr\""

programming_language: Shell
filename: docker-compose.minio.yml
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,14 @@
+x-allow-minio: &allow-minio
+  depends_on:
+    - minio
+  volumes:
+    - ./tests/allow_minio.sh:/etc/cvat/init.d/allow_minio.sh:ro
+
 services:
+  cvat_server: *allow-minio
+  cvat_worker_export: *allow-minio
+  cvat_worker_import: *allow-minio
+
   minio:
     image: quay.io/minio/minio:RELEASE.2022-09-17T00-09-45Z
     hostname: minio

programming_language: Shell