Exposure of hashed user passwords via REST API in Nautobot
Description
Nautobot is a Network Automation Platform built as a web application atop the Django Python framework with a PostgreSQL or MySQL database. In Nautobot 2.0.x, certain REST API endpoints, in combination with the ?depth= query parameter, can expose hashed user passwords as stored in the database to any authenticated user with access to these endpoints. The passwords are not exposed in plaintext. This vulnerability has been patched in version 2.0.3.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In Nautobot 2.0.x, certain REST API endpoints with the `?depth=N` parameter expose hashed user passwords to any authenticated user; patched in 2.0.3.
In Nautobot 2.0.x, a vulnerability in the REST API allows any authenticated user to retrieve hashed user passwords by appending the ?depth= query parameter to certain endpoints [1][2]. The root cause is that the depth parameter causes nested serializers to include sensitive fields, such as the password field, which are normally excluded from API responses [2]. The example in the advisory shows that a GET request to /api/users/permissions/?depth=1 returns the password hash (PBKDF2) for each user in the response [2].
Exploitation requires only that the attacker is an authenticated user with access to the affected REST API endpoints. No special privileges or administrative access are needed beyond standard authentication [1][2]. The attack surface is the API itself, and the depth parameter is a legitimate feature that inadvertently exposes sensitive data when used with certain endpoints.
The impact is that hashed passwords are exposed to unauthorized users. While the passwords are not in plaintext, the hashes (PBKDF2) can be subjected to offline brute-force or dictionary attacks, potentially leading to account compromise [2]. This vulnerability affects Nautobot 2.0.x only; Nautobot 1.x is not vulnerable [2].
The issue has been patched in Nautobot version 2.0.3 [1][4]. The fix, implemented in pull request #4692, addresses the inheritance of Meta class attributes in nested serializer classes to prevent the inclusion of sensitive fields [4]. Users are strongly advised to upgrade to 2.0.3 or later to mitigate the risk.
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.
| Package | Affected versions | Patched versions |
|---|---|---|
nautobotPyPI | >= 2.0.0, < 2.0.3 | 2.0.3 |
Affected products
2- nautobot/nautobotv5Range: >= 2.0.0, < 2.0.3
Patches
11ce8e5c658a0Fix `Meta` inheritance in nested serializer classes (#4692)
6 files changed · +39 −7
changes/4692.fixed+1 −0 added@@ -0,0 +1 @@ +Fixed incorrect inheritance of `Meta` attributes into nested serializers (`depth >= 1`).
changes/4692.housekeeping+1 −0 added@@ -0,0 +1 @@ +Added check in REST API generic test cases to detect strings like `password` and `sha256` that shouldn't generally appear in REST API responses.
changes/4692.security+1 −0 added@@ -0,0 +1 @@ +Fixed potential exposure of hashed user password data on certain REST API endpoints when using the `?depth=1` query parameter. For more details, please refer to [GHSA-r2hw-74xv-4gqp](https://github.com/nautobot/nautobot/security/advisories/GHSA-r2hw-74xv-4gqp).
nautobot/core/api/utils.py+1 −6 modified@@ -314,14 +314,9 @@ def nested_serializer_factory(relation_info, nested_depth): base_serializer_class = get_serializer_for_model(relation_info.related_model) class NautobotNestedSerializer(base_serializer_class): - class Meta: - model = relation_info.related_model + class Meta(base_serializer_class.Meta): is_nested = True depth = nested_depth - 1 - if hasattr(base_serializer_class.Meta, "fields"): - fields = base_serializer_class.Meta.fields - if hasattr(base_serializer_class.Meta, "exclude"): - exclude = base_serializer_class.Meta.exclude NautobotNestedSerializer.__name__ = nested_serializer_name NESTED_SERIALIZER_CACHE[nested_serializer_name] = NautobotNestedSerializer
nautobot/core/testing/api.py+32 −1 modified@@ -73,6 +73,31 @@ def _get_list_url(self): viewname = lookup.get_route_for_model(self.model, "list", api=True) return reverse(viewname) + VERBOTEN_STRINGS = ( + "password", + # https://docs.djangoproject.com/en/3.2/topics/auth/passwords/#included-hashers + "argon2", + "bcrypt", + "crypt", + "md5", + "pbkdf2", + "scrypt", + "sha1", + "sha256", + "sha512", + ) + + def assert_no_verboten_content(self, response): + """ + Check an API response for content that should not be exposed in the API. + + If a specific API has a false failure here (maybe it has security-related strings as model flags or something?), + its test case should overload self.VERBOTEN_STRINGS appropriately. + """ + response_raw_content = response.content.decode(response.charset) + for verboten in self.VERBOTEN_STRINGS: + self.assertNotIn(verboten, response_raw_content) + @tag("unit") class APIViewTestCases: @@ -150,6 +175,8 @@ def test_get_object(self): # Fields that should be absent by default (opt-in fields): self.assertNotIn("computed_fields", response.data) self.assertNotIn("relationships", response.data) + # Content that should never be present: + self.assert_no_verboten_content(response) # If opt-in fields are supported on this model, make sure they can be opted into @@ -302,6 +329,7 @@ def test_list_objects_depth_0(self): self.assertIsInstance(response.data, dict) self.assertIn("results", response.data) self.assertEqual(len(response.data["results"]), self._get_queryset().count()) + self.assert_no_verboten_content(response) for response_data in response.data["results"]: for field in depth_fields: @@ -316,7 +344,8 @@ def test_list_objects_depth_0(self): url = response_data[field]["url"] pk = response_data[field]["id"] object_type = response_data[field]["object_type"] - # The response should be a brief API object, containing an ID, object_type, and URL ending in the UUID of the relevant object + # The response should be a brief API object, containing an ID, object_type, and a + # URL ending in the UUID of the relevant object: # http://nautobot.example.com/api/circuits/providers/<uuid>/ # ^^^^^^ self.assertTrue(is_uuid(url.split("/")[-2])) @@ -340,6 +369,7 @@ def test_list_objects_depth_1(self): self.assertIsInstance(response.data, dict) self.assertIn("results", response.data) self.assertEqual(len(response.data["results"]), self._get_queryset().count()) + self.assert_no_verboten_content(response) for response_data in response.data["results"]: for field in depth_fields: @@ -392,6 +422,7 @@ def test_list_objects(self): self.assertIsInstance(response.data, dict) self.assertIn("results", response.data) self.assertEqual(len(response.data["results"]), 2) + self.assert_no_verboten_content(response) @override_settings(EXEMPT_VIEW_PERMISSIONS=[]) def test_list_objects_filtered(self):
nautobot/docs/development/apps/migration/code-updates.md+3 −0 modified@@ -123,6 +123,9 @@ App Model Serializers for any models that could have a Generic Foreign Key or a After removing existing `NestedSerializers`, you can change the `fields` attribute in your serializers' `class Meta` to `__all__` and that will automatically include all the model's fields in the serializer, including related-model fields that would previously have required a reference to a `NestedSerializer`. If you want to exclude certain fields of the model, you can specify a list of fields you want to display in the `fields` attribute instead. +!!! warning + Use caution around `fields = "__all__"` -- if your model has any fields that should _not_ be exposed in the REST API, you should avoid using `"__all__"` and instead use an explicit `fields` list to ensure that such fields are not exposed. In some cases, it may be appropriate to use `"__all__"` in combination with flags such as `write_only=True` on specific fields, but proceed with caution and examine the REST API data carefully to ensure that its contents are as expected. + Include all model attributes: ```python
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-r2hw-74xv-4gqpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-46128ghsaADVISORY
- github.com/nautobot/nautobot/commit/1ce8e5c658a075c29554d517cd453675e5d40d71ghsax_refsource_MISCWEB
- github.com/nautobot/nautobot/pull/4692ghsax_refsource_MISCWEB
- github.com/nautobot/nautobot/security/advisories/GHSA-r2hw-74xv-4gqpghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/nautobot/PYSEC-2023-220.yamlghsaWEB
News mentions
0No linked articles in our index yet.