GHSA-R9R3-G9FP-3Q4Q

Vulnerability from github – Published: 2026-08-19 19:32 – Updated: 2026-08-19 19:32
VLAI
Summary
Snipe-IT: Maintenance Record Disclosure via Missing Authorization on GET
Details

Impact

Any activated account in a company can read every maintenance record for that company (asset tag, supplier, purchase cost, free-text notes, dates) without holding any asset or maintenance permission.

Summary

MaintenancesController::show() renders a maintenance record without any authorization check. Every other action in the controller authorizes against the asset; show() does not. Any user in the asset's company can read maintenance detail (asset tag, supplier, purchase cost, notes, dates) by visiting /maintenances/{id}, regardless of permissions.

Details

public function show(Maintenance $maintenance): View|RedirectResponse
{
    return view('maintenances.view')->with('maintenance', $maintenance);
}

No authorize() call. The sibling actions all gate on the asset: index() calls authorize('view', Asset::class) (line 33), and edit()/update()/destroy() call authorize('update', $maintenance->asset) (lines 139, 166, 286). The route is registered with only the auth guard:

Route::resource('maintenances', MaintenancesController::class, ['middleware' => ['auth']]);

(routes/web/hardware.php:185). Route-model binding still applies the company scope, so the read is bounded to the caller's company; the absent permission gate is the defect. Maintenance IDs are sequential and visible in the record URL.

Proof of concept

  1. As an administrator, create an asset in a company (here, CompanyA). Open the asset, choose Maintenances > Create, and add a record: name MntA2, supplier SupA, a purchase cost, and notes. The saved record opens at /maintenances/{id}.
  2. As the administrator, create a test user assigned to CompanyA, with every permission left unchecked. Activate the account.
  3. In a separate browser session, log in as the test user. Confirm it is unprivileged: the Assets and Maintenances navigation items are absent, and browsing to /hardware returns 403.
  4. In the address bar, browse to http://<host>/maintenances/{id}.

Observed: the maintenance view renders in full for the unprivileged account.

GET /maintenances/5      ->  HTTP 200 OK
Renders the "Maintenance" detail page for MntA2:
  Asset: AssetA   Supplier: SupA   Cost: <value>   Notes: <text>   Dates: <...>

GET /hardware            ->  HTTP 403  (same account, asset list is gated)
GET /maintenances        ->  HTTP 403  (same account, maintenance list is gated)
GET /maintenances/2      ->  HTTP 302  (record in CompanyB; company scope still hides it)
  • The test account holds zero permissions and still reads the record.
  • Only the unguarded show route leaks: the list view and the asset pages return 403 for the same account.
  • A maintenance in a different company (CompanyB) redirects away, confirming the FMCS company scope still holds.

Patches

Patched in https://github.com/grokability/snipe-it/commit/69c50aa2aee25f837626556b4f4f3d05ec7ace96

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "snipe/snipe-it"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "8.6.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55703"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-19T19:32:25Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Impact\nAny activated account in a company can read every maintenance record for that company (asset tag, supplier, purchase cost, free-text notes, dates) without holding any asset or maintenance permission.\n\n## Summary\n\n`MaintenancesController::show()` renders a maintenance record without any authorization check. Every other action in the controller authorizes against the asset; `show()` does not. Any user in the asset\u0027s company can read maintenance detail (asset tag, supplier, purchase cost, notes, dates) by visiting `/maintenances/{id}`, regardless of permissions.\n\n## Details\n\n```php\npublic function show(Maintenance $maintenance): View|RedirectResponse\n{\n    return view(\u0027maintenances.view\u0027)-\u003ewith(\u0027maintenance\u0027, $maintenance);\n}\n```\n\nNo `authorize()` call. The sibling actions all gate on the asset: `index()` calls `authorize(\u0027view\u0027, Asset::class)` (line 33), and `edit()`/`update()`/`destroy()` call `authorize(\u0027update\u0027, $maintenance-\u003easset)` (lines 139, 166, 286). The route is registered with only the auth guard:\n\n```php\nRoute::resource(\u0027maintenances\u0027, MaintenancesController::class, [\u0027middleware\u0027 =\u003e [\u0027auth\u0027]]);\n```\n\n(`routes/web/hardware.php:185`). Route-model binding still applies the company scope, so the read is bounded to the caller\u0027s company; the absent permission gate is the defect. Maintenance IDs are sequential and visible in the record URL.\n\n## Proof of concept\n\n1. As an administrator, create an asset in a company (here, `CompanyA`). Open the asset, choose Maintenances \u003e Create, and add a record: name `MntA2`, supplier `SupA`, a purchase cost, and notes. The saved record opens at `/maintenances/{id}`.\n2. As the administrator, create a test user assigned to CompanyA, with **every permission left unchecked**. Activate the account.\n3. In a separate browser session, log in as the test user. Confirm it is unprivileged: the Assets and Maintenances navigation items are absent, and browsing to `/hardware` returns 403.\n4. In the address bar, browse to `http://\u003chost\u003e/maintenances/{id}`.\n\nObserved: the maintenance view renders in full for the unprivileged account.\n\n```\nGET /maintenances/5      -\u003e  HTTP 200 OK\nRenders the \"Maintenance\" detail page for MntA2:\n  Asset: AssetA   Supplier: SupA   Cost: \u003cvalue\u003e   Notes: \u003ctext\u003e   Dates: \u003c...\u003e\n\nGET /hardware            -\u003e  HTTP 403  (same account, asset list is gated)\nGET /maintenances        -\u003e  HTTP 403  (same account, maintenance list is gated)\nGET /maintenances/2      -\u003e  HTTP 302  (record in CompanyB; company scope still hides it)\n```\n\n- The `test` account holds zero permissions and still reads the record.\n- Only the unguarded `show` route leaks: the list view and the asset pages return 403 for the same account.\n- A maintenance in a different company (CompanyB) redirects away, confirming the FMCS company scope still holds.\n\n### Patches\nPatched in https://github.com/grokability/snipe-it/commit/69c50aa2aee25f837626556b4f4f3d05ec7ace96",
  "id": "GHSA-r9r3-g9fp-3q4q",
  "modified": "2026-08-19T19:32:25Z",
  "published": "2026-08-19T19:32:25Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/grokability/snipe-it/security/advisories/GHSA-r9r3-g9fp-3q4q"
    },
    {
      "type": "WEB",
      "url": "https://github.com/grokability/snipe-it/commit/69c50aa2aee25f837626556b4f4f3d05ec7ace96"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/grokability/snipe-it"
    },
    {
      "type": "WEB",
      "url": "https://github.com/grokability/snipe-it/releases/tag/v8.6.3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Snipe-IT: Maintenance Record Disclosure via Missing Authorization on GET"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…