GHSA-x287-5c68-36wp: Medium severity pip/openwisp-ipam vulnerability

Published Aug 26, 2026
·
Updated

Summary

OpenWISP IPAM is multi-tenant: every Subnet belongs to an organization, and API access is scoped to the organizations a user belongs to. The CSV export endpoint, ExportSubnetView, omits the organization-membership check that its import sibling performs, and loads the subnet by primary key with no organization filter. An authenticated user who is a member of one organization can therefore export a subnet belonging to another organization — its name, CIDR, organization slug and every IP address in it — by issuing an export request for that subnet's id.

Details

ImportSubnetView.post() authorizes first, by calling assertorganizationpermissions():

python openwispipam/api/views.py (ImportSubnetView) def post(self, request, args, kwargs): self.assertorganizationpermissions(request) # <-- org-membership check file = request.FILES["csvfile"] ... self.subnetmodel().importcsv(file) python openwispipam/api/utils.py:16 (AuthorizeCSVImport) def assertorganizationpermissions(self, request): if request.user.issuperuser: return # ... otherwise verifies the user belongs to the target organization

ExportSubnetView.post() performs no such check. It is declared with IpAddressOrgMixin (whose organization scoping lives in getqueryset()), but its overridden post() never calls getqueryset() or assertorganizationpermissions() — it goes straight to exportcsv():

python openwispipam/api/views.py:246 class ExportSubnetView(ProtectedAPIMixin, IpAddressOrgMixin, CreateAPIView): subnetmodel = Subnet def post(self, request, args, kwargs): response = HttpResponse(contenttype="text/csv") response["Content-Disposition"] = 'attachment; filename="ipaddress.csv"' writer = csv.writer(response) self.subnetmodel().exportcsv(kwargs["subnetid"], writer) # no org check reached return response

exportcsv() resolves the subnet by primary key with no organization filter and writes its full contents:

python openwispipam/base/models.py:242 def exportcsv(self, subnetid, writer): ipaddressmodel = loadmodel("openwispipam", "IpAddress") subnet = loadmodel("openwispipam", "Subnet").objects.get(pk=subnetid) # any org's subnet # ... writes subnet name, CIDR, organization slug, and every IpAddress (address, description, …)

The subnetid is a random UUIDField (migrations/0001initial.py: models.UUIDField(default=uuid.uuid4)), so an attacker must obtain the target subnet's id rather than enumerate it — hence Attack Complexity: High. This is a genuine missing-authorization flaw regardless: the import/export asymmetry shows the check was intended, and subnet ids routinely appear in URLs, logs, shared CSV exports and support tickets, from which a cross-organization id can leak.

The same single-object pattern (resolving the subnet by subnetid outside the org-scoped queryset) also appears in AvailableIpView.get() and RequestIPView.post() (the latter additionally writes an IP into the target subnet); these are worth reviewing alongside the export fix.

Proof of concept

Prerequisites: an OpenWISP IPAM instance with two organizations OrgA and OrgB; a normal (non-superuser) user who is a member of OrgB only; a subnet in OrgA whose id S is known to the attacker (e.g. leaked via a URL/log/shared export).

1. As the OrgB user, authenticate to the API. 2. POST /api/v1/subnet/{S}/export/ (OrgA's subnet id). 3. Result: a CSV download containing OrgA's subnet name, CIDR, organization slug and every IP address in it — even though the user belongs only to OrgB. 4. Control (proves it's an oversight): the corresponding import endpoint POST /api/v1/import-subnet/ with OrgA data is rejected for the same user by assertorganizationpermissions().

(Reported from a first-hand source review of the current master: the missing check in ExportSubnetView.post, the guarded ImportSubnetView.post sibling, the un-filtered Subnet.objects.get(pk=…) in exportcsv, and the UUIDField primary key were each verified by hand. The fix is a one-line authorization call, so a maintainer can confirm trivially with two organizations.)

Impact

A member of one organization can read the complete contents of another organization's subnet — its IP allocation inventory (addresses, descriptions, organization slug, CIDR). In a WISP / network-management deployment this discloses another tenant's network topology and host inventory. Exploitation requires obtaining the target subnet's UUID (AC:H), but the authorization check is missing outright. (RequestIPView shares the pattern and additionally allows writing an IP into another organization's subnet — a separate integrity concern for the maintainer to confirm.)

Remediation

Add the organization-membership check to ExportSubnetView.post() — call self.assertorganizationpermissions(request) (as ImportSubnetView does), or resolve the subnet through the org-scoped getqueryset() / getobject() and return 404 when it isn't in the caller's organizations. Apply the same to AvailableIpView and RequestIPView.

Affected Software

1 affected componentFixes available
pip/openwisp-ipam<=1.2.0.post1
1.2.1

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/openwisp-ipam to a version that resolves this vulnerability.

    Fixed in 1.2.1
  2. Configuration

    In `ExportSubnetView.post()`, add an organization-membership check by calling `self.assert_organization_permissions(request)` (mirroring `ImportSubnetView.post()`). This ensures the subnet loaded/exported belongs to one of the caller's organizations; otherwise resolve via the org-scoped `get_queryset()`/`get_object()` and return 404.

    OpenWISP IPAM API (ExportSubnetView.post) organization-membership authorization check = enabled
  3. Configuration

    In `export_csv(subnet_id, writer)`, stop resolving the subnet via `Subnet.objects.get(pk=subnet_id)` without an organization filter. Instead resolve the subnet through the org-scoped queryset/get_object so cross-organization UUIDs return 404.

    OpenWISP IPAM API (ExportSubnetView.export_csv) subnet lookup scope = organization-scoped
  4. Configuration

    Review and apply the same single-object pattern used in the export fix: ensure `AvailableIpView.get()` resolves the target subnet/IP data via the org-scoped queryset or by calling `assert_organization_permissions()` so callers cannot access another organization's inventory.

    OpenWISP IPAM API (AvailableIpView.get) organization-membership enforcement in post/get = enabled
  5. Configuration

    Review and apply the same single-object pattern used in the export fix: ensure `RequestIPView.post()` authorizes/resolves the target subnet/IP write via the org-scoped queryset or by calling `assert_organization_permissions()` so callers cannot write IPs into another organization's subnet.

    OpenWISP IPAM API (RequestIPView.post) organization-membership enforcement in post/get = enabled

Event History

Aug 26, 2026
Advisory Published
via GitHub·02:38 PM
Data Sourced
via GitHub·02:38 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What access and information does an attacker need to retrieve another tenant's subnet data?

The attacker must be authenticated and belong to at least one organization. They also need the primary-key ID of the target subnet and can submit an export request for that ID; they do not need membership in the target organization.

2

What information can be disclosed through the unauthorized export?

The export can disclose the target subnet's name, CIDR, organization slug, and every IP address contained in that subnet.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203