VYPR
Low severityNVD Advisory· Published Dec 12, 2023· Updated Aug 2, 2024

Nautobot allows unauthenticated db-file-storage views

CVE-2023-50263

Description

Nautobot is a Network Source of Truth and Network Automation Platform built as a web application atop the Django Python framework with a PostgreSQL or MySQL database. In Nautobot 1.x and 2.0.x prior to 1.6.7 and 2.0.6, the URLs /files/get/?name=... and /files/download/?name=... are used to provide admin access to files that have been uploaded as part of a run request for a Job that has FileVar inputs. Under normal operation these files are ephemeral and are deleted once the Job in question runs.

In the default implementation used in Nautobot, as provided by django-db-file-storage, these URLs do not by default require any user authentication to access; they should instead be restricted to only users who have permissions to view Nautobot's FileProxy model instances.

Note that no URL mechanism is provided for listing or traversal of the available file name values, so in practice an unauthenticated user would have to guess names to discover arbitrary files for download, but if a user knows the file name/path value, they can access it without authenticating, so we are considering this a vulnerability.

Fixes are included in Nautobot 1.6.7 and Nautobot 2.0.6. No known workarounds are available other than applying the patches included in those versions.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Nautobot file download/view URLs lack authentication, allowing unauthenticated access to uploaded job files if the filename is known.

Vulnerability

CVE-2023-50263 is an authentication bypass vulnerability in Nautobot, a Network Source of Truth and Network Automation Platform. The issue affects Nautobot versions 1.x and 2.0.x prior to 1.6.7 and 2.0.6. The URLs /files/get/?name=... and /files/download/?name=... are used to provide admin access to files uploaded as part of a Job run request that includes FileVar inputs. In the default implementation using django-db-file-storage, these URLs do not require any user authentication, contrary to the intended design that would restrict access to users with permissions on the FileProxy model instances [1].

Exploitation

To exploit this vulnerability, an attacker must know the exact file name/path value of a target file. The application does not provide any mechanism for listing or enumerating available file names, so exploitation requires guessing or prior knowledge of a specific file name. No authentication is needed to access the file if the name is known [1]. The files are ephemeral and normally deleted after the job runs, but an attacker who identifies a valid name before deletion can download it [1].

Impact

An unauthenticated attacker who knows a valid file name can download or view files uploaded as part of job run requests. These files may contain sensitive data from network automation tasks, such as configuration files, credentials, or other operational data. The vulnerability is classified with a CVSS score and affects the confidentiality of the system [1].

Mitigation

Fixes are included in Nautobot versions 1.6.7 and 2.0.6. The patches add authentication and permission checks to the DB file storage views [2][3][4]. No workarounds are available; users must upgrade to the patched versions [1].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
nautobotPyPI
>= 1.1.0, < 1.6.71.6.7
nautobotPyPI
>= 2.0.0, < 2.0.62.0.6

Affected products

2
  • ghsa-coords
    Range: >= 1.1.0, < 1.6.7
  • nautobot/nautobotv5
    Range: >= 1.1.0, < 1.6.7

Patches

3
7c4cf3137f45

[1.6] Require authentication on DB file storage views (#4964)

https://github.com/nautobot/nautobotGlenn MatthewsDec 12, 2023via ghsa
4 files changed · +98 5
  • changes/4959.security+1 0 added
    @@ -0,0 +1 @@
    +Enforce authentication and object permissions on DB file storage views (https://github.com/nautobot/nautobot/security/advisories/GHSA-75mc-3pjc-727q).
    
  • nautobot/core/tests/test_views.py+61 0 modified
    @@ -2,12 +2,17 @@
     from unittest import mock
     import urllib.parse
     
    +from django.contrib.contenttypes.models import ContentType
    +from django.core.files.uploadedfile import SimpleUploadedFile
     from django.test import override_settings
     from django.test.utils import override_script_prefix
     from django.urls import get_script_prefix, reverse
     from prometheus_client.parser import text_string_to_metric_families
     
    +from nautobot.extras.models import FileProxy
     from nautobot.extras.registry import registry
    +from nautobot.users.models import ObjectPermission
    +from nautobot.utilities.permissions import get_permission_for_model
     from nautobot.utilities.testing import TestCase
     
     
    @@ -408,3 +413,59 @@ def test_500_custom_support_message(self, mock_get):
             self.assertNotContains(response, "Network to Code", status_code=500)
             response_content = response.content.decode(response.charset)
             self.assertInHTML("Hello world!", response_content)
    +
    +
    +class DBFileStorageViewTestCase(TestCase):
    +    """Test authentication/permission enforcement for django_db_file_storage views."""
    +
    +    def setUp(self):
    +        super().setUp()
    +        self.test_file_1 = SimpleUploadedFile(name="test_file_1.txt", content=b"I am content.\n")
    +        self.file_proxy_1 = FileProxy.objects.create(name=self.test_file_1.name, file=self.test_file_1)
    +        self.test_file_2 = SimpleUploadedFile(name="test_file_2.txt", content=b"I am content.\n")
    +        self.file_proxy_2 = FileProxy.objects.create(name=self.test_file_2.name, file=self.test_file_2)
    +        self.urls = [
    +            f"{reverse('db_file_storage.download_file')}?name={self.file_proxy_1.file.name}",
    +            f"{reverse('db_file_storage.get_file')}?name={self.file_proxy_1.file.name}",
    +        ]
    +
    +    def test_get_file_anonymous(self):
    +        self.client.logout()
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 403)
    +
    +    def test_get_file_without_permission(self):
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 403)
    +
    +    def test_get_object_with_permission(self):
    +        self.add_permissions(get_permission_for_model(FileProxy, "view"))
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 200)
    +
    +    def test_get_object_with_constrained_permission(self):
    +        obj_perm = ObjectPermission(
    +            name="Test permission",
    +            constraints={"pk": self.file_proxy_1.pk},
    +            actions=["view"],
    +        )
    +        obj_perm.save()
    +        obj_perm.users.add(self.user)
    +        obj_perm.object_types.add(ContentType.objects.get_for_model(FileProxy))
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 200)
    +        for url in [
    +            f"{reverse('db_file_storage.download_file')}?name={self.file_proxy_2.file.name}",
    +            f"{reverse('db_file_storage.get_file')}?name={self.file_proxy_2.file.name}",
    +        ]:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 404)
    
  • nautobot/core/urls.py+21 3 modified
    @@ -1,9 +1,16 @@
     from django.conf import settings
    -from django.conf.urls import include
    +from django.conf.urls import include, url
     from django.urls import path
     from django.views.static import serve
     
    -from nautobot.core.views import CustomGraphQLView, HomeView, StaticMediaFailureView, SearchView, nautobot_metrics_view
    +from nautobot.core.views import (
    +    CustomGraphQLView,
    +    HomeView,
    +    StaticMediaFailureView,
    +    SearchView,
    +    nautobot_metrics_view,
    +    get_file_with_authorization,
    +)
     from nautobot.extras.plugins.urls import (
         plugin_admin_patterns,
         plugin_patterns,
    @@ -46,7 +53,18 @@
         # django-health-check
         path(r"health/", include("health_check.urls")),
         # FileProxy attachments download/get URLs used in admin views only
    -    path("files/", include("db_file_storage.urls")),
    +    url(
    +        "files/download/",
    +        get_file_with_authorization,
    +        {"add_attachment_headers": True},
    +        name="db_file_storage.download_file",
    +    ),
    +    url(
    +        "files/get/",
    +        get_file_with_authorization,
    +        {"add_attachment_headers": False},
    +        name="db_file_storage.get_file",
    +    ),
     ]
     
     
    
  • nautobot/core/views/__init__.py+15 2 modified
    @@ -3,11 +3,13 @@
     import sys
     import time
     
    +from db_file_storage.views import get_file
     import prometheus_client
     from django.conf import settings
    +from django.contrib.auth.decorators import permission_required
     from django.contrib.auth.mixins import AccessMixin
     from django.http import HttpResponseServerError, JsonResponse, HttpResponseForbidden, HttpResponse
    -from django.shortcuts import redirect, render
    +from django.shortcuts import get_object_or_404, redirect, render
     from django.template import loader, RequestContext, Template
     from django.template.exceptions import TemplateDoesNotExist
     from django.urls import reverse
    @@ -24,10 +26,11 @@
     from nautobot.core.constants import SEARCH_MAX_RESULTS, SEARCH_TYPES
     from nautobot.core.forms import SearchForm
     from nautobot.core.releases import get_latest_release
    -from nautobot.extras.models import GraphQLQuery
    +from nautobot.extras.models import GraphQLQuery, FileProxy
     from nautobot.extras.registry import registry
     from nautobot.extras.forms import GraphQLQueryForm
     from nautobot.utilities.config import get_settings_or_config
    +from nautobot.utilities.permissions import get_permission_for_model
     
     
     class HomeView(AccessMixin, TemplateView):
    @@ -278,3 +281,13 @@ def nautobot_metrics_view(request):
             pass
         metrics_page = prometheus_client.generate_latest(prometheus_registry)
         return HttpResponse(metrics_page, content_type=prometheus_client.CONTENT_TYPE_LATEST)
    +
    +
    +@permission_required(get_permission_for_model(FileProxy, "view"), raise_exception=True)
    +def get_file_with_authorization(request, *args, **kwargs):
    +    """Patch db_file_storage view with authentication."""
    +    # Make sure user has permissions
    +    queryset = FileProxy.objects.restrict(request.user, "view")
    +    get_object_or_404(queryset, file=request.GET.get("name"))
    +
    +    return get_file(request, *args, **kwargs)
    
458280c359a4

requires authentication on DB file storage views (#4959)

https://github.com/nautobot/nautobotLeo KirchnerDec 12, 2023via ghsa
4 files changed · +97 5
  • changes/4959.security+1 0 added
    @@ -0,0 +1 @@
    +Enforce authentication and object permissions on DB file storage views (https://github.com/nautobot/nautobot/security/advisories/GHSA-75mc-3pjc-727q).
    
  • nautobot/core/tests/test_views.py+60 0 modified
    @@ -3,17 +3,21 @@
     import urllib.parse
     
     from django.contrib.contenttypes.models import ContentType
    +from django.core.files.uploadedfile import SimpleUploadedFile
     from django.test import RequestFactory, override_settings
     from django.test.utils import override_script_prefix
     from django.urls import get_script_prefix, reverse
     from prometheus_client.parser import text_string_to_metric_families
     
     from nautobot.core.testing import TestCase
    +from nautobot.core.utils.permissions import get_permission_for_model
     from nautobot.core.views.mixins import GetReturnURLMixin
     from nautobot.dcim.models.locations import Location
     from nautobot.extras.choices import CustomFieldTypeChoices
    +from nautobot.extras.models import FileProxy
     from nautobot.extras.models.customfields import CustomField, CustomFieldChoice
     from nautobot.extras.registry import registry
    +from nautobot.users.models import ObjectPermission
     
     
     class GetReturnURLMixinTestCase(TestCase):
    @@ -476,3 +480,59 @@ def test_500_custom_support_message(self, mock_get):
             self.assertNotContains(response, "Network to Code", status_code=500)
             response_content = response.content.decode(response.charset)
             self.assertInHTML("Hello world!", response_content)
    +
    +
    +class DBFileStorageViewTestCase(TestCase):
    +    """Test authentication/permission enforcement for django_db_file_storage views."""
    +
    +    def setUp(self):
    +        super().setUp()
    +        self.test_file_1 = SimpleUploadedFile(name="test_file_1.txt", content=b"I am content.\n")
    +        self.file_proxy_1 = FileProxy.objects.create(name=self.test_file_1.name, file=self.test_file_1)
    +        self.test_file_2 = SimpleUploadedFile(name="test_file_2.txt", content=b"I am content.\n")
    +        self.file_proxy_2 = FileProxy.objects.create(name=self.test_file_2.name, file=self.test_file_2)
    +        self.urls = [
    +            f"{reverse('db_file_storage.download_file')}?name={self.file_proxy_1.file.name}",
    +            f"{reverse('db_file_storage.get_file')}?name={self.file_proxy_1.file.name}",
    +        ]
    +
    +    def test_get_file_anonymous(self):
    +        self.client.logout()
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 403)
    +
    +    def test_get_file_without_permission(self):
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 403)
    +
    +    def test_get_object_with_permission(self):
    +        self.add_permissions(get_permission_for_model(FileProxy, "view"))
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 200)
    +
    +    def test_get_object_with_constrained_permission(self):
    +        obj_perm = ObjectPermission(
    +            name="Test permission",
    +            constraints={"pk": self.file_proxy_1.pk},
    +            actions=["view"],
    +        )
    +        obj_perm.save()
    +        obj_perm.users.add(self.user)
    +        obj_perm.object_types.add(ContentType.objects.get_for_model(FileProxy))
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 200)
    +        for url in [
    +            f"{reverse('db_file_storage.download_file')}?name={self.file_proxy_2.file.name}",
    +            f"{reverse('db_file_storage.get_file')}?name={self.file_proxy_2.file.name}",
    +        ]:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 404)
    
  • nautobot/core/urls.py+21 3 modified
    @@ -1,9 +1,16 @@
     from django.conf import settings
    -from django.conf.urls import include
    +from django.conf.urls import include, url
     from django.urls import path
     from django.views.static import serve
     
    -from nautobot.core.views import CustomGraphQLView, HomeView, StaticMediaFailureView, SearchView, nautobot_metrics_view
    +from nautobot.core.views import (
    +    CustomGraphQLView,
    +    HomeView,
    +    StaticMediaFailureView,
    +    SearchView,
    +    nautobot_metrics_view,
    +    get_file_with_authorization,
    +)
     from nautobot.extras.plugins.urls import (
         plugin_admin_patterns,
         plugin_patterns,
    @@ -45,7 +52,18 @@
         # django-health-check
         path(r"health/", include("health_check.urls")),
         # FileProxy attachments download/get URLs used in admin views only
    -    path("files/", include("db_file_storage.urls")),
    +    url(
    +        "files/download/",
    +        get_file_with_authorization,
    +        {"add_attachment_headers": True},
    +        name="db_file_storage.download_file",
    +    ),
    +    url(
    +        "files/get/",
    +        get_file_with_authorization,
    +        {"add_attachment_headers": False},
    +        name="db_file_storage.get_file",
    +    ),
     ]
     
     
    
  • nautobot/core/views/__init__.py+15 2 modified
    @@ -3,12 +3,14 @@
     import sys
     import time
     
    +from db_file_storage.views import get_file
     from django.apps import apps
     import prometheus_client
     from django.conf import settings
    +from django.contrib.auth.decorators import permission_required
     from django.contrib.auth.mixins import AccessMixin
     from django.http import HttpResponseServerError, JsonResponse, HttpResponseForbidden, HttpResponse
    -from django.shortcuts import redirect, render
    +from django.shortcuts import get_object_or_404, redirect, render
     from django.template import loader, RequestContext, Template
     from django.template.exceptions import TemplateDoesNotExist
     from django.urls import resolve, reverse
    @@ -27,7 +29,8 @@
     from nautobot.core.releases import get_latest_release
     from nautobot.core.utils.config import get_settings_or_config
     from nautobot.core.utils.lookup import get_route_for_model
    -from nautobot.extras.models import GraphQLQuery
    +from nautobot.core.utils.permissions import get_permission_for_model
    +from nautobot.extras.models import GraphQLQuery, FileProxy
     from nautobot.extras.registry import registry
     from nautobot.extras.forms import GraphQLQueryForm
     
    @@ -294,3 +297,13 @@ def nautobot_metrics_view(request):
             pass
         metrics_page = prometheus_client.generate_latest(prometheus_registry)
         return HttpResponse(metrics_page, content_type=prometheus_client.CONTENT_TYPE_LATEST)
    +
    +
    +@permission_required(get_permission_for_model(FileProxy, "view"), raise_exception=True)
    +def get_file_with_authorization(request, *args, **kwargs):
    +    """Patch db_file_storage view with authentication."""
    +    # Make sure user has permissions
    +    queryset = FileProxy.objects.restrict(request.user, "view")
    +    get_object_or_404(queryset, file=request.GET.get("name"))
    +
    +    return get_file(request, *args, **kwargs)
    
5e2ba9e8ac08

requires authentication on DB file storage views

https://github.com/nautobot/nautobotLeo KirchnerDec 12, 2023via ghsa
4 files changed · +62 5
  • changes/4959.security+1 0 added
    @@ -0,0 +1 @@
    +Require authentication on DB file storage views (https://github.com/nautobot/nautobot/security/advisories/GHSA-75mc-3pjc-727q).
    \ No newline at end of file
    
  • nautobot/core/tests/test_views.py+30 1 modified
    @@ -2,13 +2,16 @@
     from unittest import mock
     import urllib.parse
     
    +from django.contrib.contenttypes.models import ContentType
    +from django.core.files.uploadedfile import SimpleUploadedFile
     from django.test import override_settings
     from django.test.utils import override_script_prefix
     from django.urls import get_script_prefix, reverse
     from prometheus_client.parser import text_string_to_metric_families
     
     from nautobot.extras.registry import registry
    -from nautobot.utilities.testing import TestCase
    +from nautobot.utilities.permissions import get_permission_for_model
    +from nautobot.utilities.testing import TestCase, User
     
     
     class HomeViewTestCase(TestCase):
    @@ -408,3 +411,29 @@ def test_500_custom_support_message(self, mock_get):
             self.assertNotContains(response, "Network to Code", status_code=500)
             response_content = response.content.decode(response.charset)
             self.assertInHTML("Hello world!", response_content)
    +
    +
    +class DBFileStorageViewTestCase(TestCase):
    +    """Test overwritten views for django_db_file_storage views"""
    +
    +    def setUp(self):
    +        self.user = User.objects.create(username="testuser")
    +        self.test_file = SimpleUploadedFile(name="test_file.txt", content=b"I am content.\n")
    +        self.file_proxy = FileProxy.objects.create(name=self.test_file.name, file=self.test_file)
    +        self.urls = [
    +            f"{reverse('db_file_storage.download_file')}?name={self.file_proxy.file.name}",
    +            f"{reverse('db_file_storage.get_file')}?name={self.file_proxy.file.name}",
    +        ]
    +
    +    def test_authorization_failed(self):
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 403)
    +
    +    def test_authorization_succeeded(self):
    +        self.add_permissions(get_permission_for_model(FileProxy, "view"))
    +        for url in self.urls:
    +            with self.subTest(url):
    +                response = self.client.get(url)
    +                self.assertHttpStatus(response, 200)
    
  • nautobot/core/urls.py+21 3 modified
    @@ -1,9 +1,16 @@
     from django.conf import settings
    -from django.conf.urls import include
    +from django.conf.urls import include, url
     from django.urls import path
     from django.views.static import serve
     
    -from nautobot.core.views import CustomGraphQLView, HomeView, StaticMediaFailureView, SearchView, nautobot_metrics_view
    +from nautobot.core.views import (
    +    CustomGraphQLView,
    +    HomeView,
    +    StaticMediaFailureView,
    +    SearchView,
    +    nautobot_metrics_view,
    +    get_file_with_authorization,
    +)
     from nautobot.extras.plugins.urls import (
         plugin_admin_patterns,
         plugin_patterns,
    @@ -46,7 +53,18 @@
         # django-health-check
         path(r"health/", include("health_check.urls")),
         # FileProxy attachments download/get URLs used in admin views only
    -    path("files/", include("db_file_storage.urls")),
    +    url(
    +        "files/download/",
    +        get_file_with_authorization,
    +        {"add_attachment_headers": True},
    +        name="db_file_storage.download_file",
    +    ),
    +    url(
    +        "files/get/",
    +        get_file_with_authorization,
    +        {"add_attachment_headers": False},
    +        name="db_file_storage.get_file",
    +    ),
     ]
     
     
    
  • nautobot/core/views/__init__.py+10 1 modified
    @@ -3,8 +3,10 @@
     import sys
     import time
     
    +from db_file_storage.views import get_file
     import prometheus_client
     from django.conf import settings
    +from django.contrib.auth.decorators import permission_required
     from django.contrib.auth.mixins import AccessMixin
     from django.http import HttpResponseServerError, JsonResponse, HttpResponseForbidden, HttpResponse
     from django.shortcuts import redirect, render
    @@ -24,10 +26,11 @@
     from nautobot.core.constants import SEARCH_MAX_RESULTS, SEARCH_TYPES
     from nautobot.core.forms import SearchForm
     from nautobot.core.releases import get_latest_release
    -from nautobot.extras.models import GraphQLQuery
    +from nautobot.extras.models import GraphQLQuery, FileProxy
     from nautobot.extras.registry import registry
     from nautobot.extras.forms import GraphQLQueryForm
     from nautobot.utilities.config import get_settings_or_config
    +from nautobot.utilities.permissions import get_permission_for_model
     
     
     class HomeView(AccessMixin, TemplateView):
    @@ -278,3 +281,9 @@ def nautobot_metrics_view(request):
             pass
         metrics_page = prometheus_client.generate_latest(prometheus_registry)
         return HttpResponse(metrics_page, content_type=prometheus_client.CONTENT_TYPE_LATEST)
    +
    +
    +@permission_required(get_permission_for_model(FileProxy, "view"), raise_exception=True)
    +def get_file_with_authorization(*args, **kwargs):
    +    """Patch db_file_storage view with authentication."""
    +    return get_file(*args, **kwargs)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.