GHSA-RRMM-9V76-H3P4

Vulnerability from github – Published: 2026-05-14 16:22 – Updated: 2026-05-14 16:22
VLAI
Summary
Portainer missing authorization on Docker plugin endpoints, which allows host RCE
Details

Summary

Portainer enforces Role-Based Access Control (RBAC) on top of the Docker API. The proxy layer routes incoming Docker API requests to per-resource handlers (containers, images, services, volumes, etc.) that apply authorization checks.

The Docker plugin management endpoints (/plugins/*) were not registered with a handler, so standard users with endpoint access could call privileged plugin operations — including installing and enabling plugins — directly against the underlying Docker daemon.

The vulnerability is exposed when a non-admin Portainer user (Standard User role, or any role granted endpoint-level access) has been given access to a Docker endpoint via Portainer RBAC. Administrators and users without Docker endpoint access are not affected.

A regular user with access to a Docker endpoint can:

  • Pull an arbitrary plugin from any registry via POST /plugins/pull.
  • Grant it the privileges it requests, including CAP_SYS_ADMIN and host-path mounts.
  • Enable the plugin via POST /plugins/{name}/enable, at which point Docker runs the plugin with root privileges on the host.

Docker plugins execute as root on the host and can request arbitrary host capabilities and mounts. Enabling a crafted plugin gives the user access to the host filesystem and equivalent to root on the Docker host.

Severity

Critical — CVSS 9.4 CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H

CWE-862 — Missing Authorization

Affected Versions

The vulnerability exists in every Portainer release where the Docker API proxy uses the prefix-allowlist routing model — /plugins has never been in the allowlist, and the fall-through path has never applied authorization.

Fixes are included in the next release of each supported branch:

Branch First vulnerable Fixed in
2.33.x (LTS) 2.33.0 2.33.8
2.39.x (LTS) 2.39.0 2.39.2
2.40.x (STS) 2.40.0 2.41.0

Portainer LTS branches receive fixes for 6 months plus a 3-month overlap after the next LTS ships. STS releases are supported only until the next STS ships — the 2.40.x STS line ends with the 2.41.0 release. All releases prior to 2.33.0 are end-of-life and will not receive a fix; users on EOL versions should upgrade to a supported LTS branch.

Workarounds

Administrators who cannot immediately upgrade can reduce exposure by temporarily revoking Docker endpoint access for non-admin users via Portainer RBAC until the patched release is deployed. This eliminates the attack surface without disruption for administrators. This does not replace the fix.

Affected Code

// api/http/proxy/factory/docker/transport.go (pre-fix)

var prefixProxyFuncMap = map[string]func(...){
    "build":      ...,
    "configs":    ...,
    "containers": ...,
    "images":     ...,
    "networks":   ...,
    "nodes":      ...,
    "secrets":    ...,
    "services":   ...,
    "swarm":      ...,
    "tasks":      ...,
    "v2":         ...,
    "volumes":    ...,
}

func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Response, error) {
    // ...
    prefix := strings.Split(strings.TrimPrefix(unversionedPath, "/"), "/")[0]

    if proxyFunc := prefixProxyFuncMap[prefix]; proxyFunc != nil {
        return proxyFunc(transport, request, unversionedPath)  // authorized
    }

    return transport.executeDockerRequest(request)  // forwarded without authorization
}

/plugins is not in prefixProxyFuncMap, so requests to plugin endpoints fall through to executeDockerRequest and are forwarded to the Docker daemon without any Portainer-side authorization check.

Impact

An authenticated, non-admin Portainer user with access to any Docker-enabled endpoint can:

  • Install and enable arbitrary Docker plugins from any registry.
  • Execute plugin code with root privileges on the Docker host (including declaring CAP_SYS_ADMIN and host-path mounts).
  • Read and modify files on the host filesystem from a restricted account, overriding the administrator's security policy.

Timeline

  • 2026-03-16: Reported via GitHub Security Advisory by ikkebr.
  • 2026-04-20: Fix merged to develop, release/2.39, and release/2.33.
  • 2026-04-29: 2.41.0 released.
  • 2026-05-07: 2.39.2-LTS and 2.33.8-LTS released.

Credit

  • ikkebr — identified and reported the proxy allowlist bypass affecting the Docker plugin management endpoints.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/portainer/portainer"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.33.0"
            },
            {
              "fixed": "2.33.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/portainer/portainer"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.39.0"
            },
            {
              "fixed": "2.39.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/portainer/portainer"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.40.0"
            },
            {
              "fixed": "2.41.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-44848"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-862"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-14T16:22:50Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\nPortainer enforces Role-Based Access Control (RBAC) on top of the Docker API. The proxy layer routes incoming Docker API requests to per-resource handlers (containers, images, services, volumes, etc.) that apply authorization checks.\n\nThe Docker plugin management endpoints (`/plugins/*`) were not registered with a handler, so standard users with endpoint access could call privileged plugin operations \u2014 including installing and enabling plugins \u2014 directly against the underlying Docker daemon.\n\nThe vulnerability is exposed when a non-admin Portainer user (Standard User role, or any role granted endpoint-level access) has been given access to a Docker endpoint via Portainer RBAC. Administrators and users without Docker endpoint access are not affected.\n\nA regular user with access to a Docker endpoint can:\n\n- Pull an arbitrary plugin from any registry via `POST /plugins/pull`.\n- Grant it the privileges it requests, including `CAP_SYS_ADMIN` and host-path mounts.\n- Enable the plugin via `POST /plugins/{name}/enable`, at which point Docker runs the plugin with root privileges on the host.\n\nDocker plugins execute as root on the host and can request arbitrary host capabilities and mounts. Enabling a crafted plugin gives the user access to the host filesystem and equivalent to root on the Docker host.\n\n## Severity\n\n**Critical** \u2014 CVSS 9.4\n`CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H`\n\n**CWE-862** \u2014 Missing Authorization\n\n## Affected Versions\n\nThe vulnerability exists in every Portainer release where the Docker API proxy uses the prefix-allowlist routing model \u2014 `/plugins` has never been in the allowlist, and the fall-through path has never applied authorization.\n\nFixes are included in the next release of each supported branch:\n\n| Branch              | First vulnerable | Fixed in   |\n|---------------------|------------------|------------|\n| 2.33.x (LTS)        | 2.33.0           | **2.33.8** |\n| 2.39.x (LTS)        | 2.39.0           | **2.39.2** |\n| 2.40.x (STS)        | 2.40.0           | **2.41.0** |\n\nPortainer LTS branches receive fixes for 6 months plus a 3-month overlap after the next LTS ships. STS releases are supported only until the next STS ships \u2014 the 2.40.x STS line ends with the 2.41.0 release. All releases **prior to 2.33.0 are end-of-life** and will not receive a fix; users on EOL versions should upgrade to a supported LTS branch.\n\n## Workarounds\n\nAdministrators who cannot immediately upgrade can reduce exposure by temporarily **revoking Docker endpoint access for non-admin users** via Portainer RBAC until the patched release is deployed. This eliminates the attack surface without disruption for administrators. This does not replace the fix.\n\n## Affected Code\n\n```go\n// api/http/proxy/factory/docker/transport.go (pre-fix)\n\nvar prefixProxyFuncMap = map[string]func(...){\n    \"build\":      ...,\n    \"configs\":    ...,\n    \"containers\": ...,\n    \"images\":     ...,\n    \"networks\":   ...,\n    \"nodes\":      ...,\n    \"secrets\":    ...,\n    \"services\":   ...,\n    \"swarm\":      ...,\n    \"tasks\":      ...,\n    \"v2\":         ...,\n    \"volumes\":    ...,\n}\n\nfunc (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Response, error) {\n    // ...\n    prefix := strings.Split(strings.TrimPrefix(unversionedPath, \"/\"), \"/\")[0]\n\n    if proxyFunc := prefixProxyFuncMap[prefix]; proxyFunc != nil {\n        return proxyFunc(transport, request, unversionedPath)  // authorized\n    }\n\n    return transport.executeDockerRequest(request)  // forwarded without authorization\n}\n```\n\n`/plugins` is not in `prefixProxyFuncMap`, so requests to plugin endpoints fall through to `executeDockerRequest` and are forwarded to the Docker daemon without any Portainer-side authorization check.\n\n\n## Impact\n\nAn authenticated, non-admin Portainer user with access to any Docker-enabled endpoint can:\n\n- Install and enable arbitrary Docker plugins from any registry.\n- Execute plugin code with root privileges on the Docker host (including declaring `CAP_SYS_ADMIN` and host-path mounts).\n- Read and modify files on the host filesystem from a restricted account, overriding the administrator\u0027s security policy.\n\n## Timeline\n\n- 2026-03-16: Reported via GitHub Security Advisory by **ikkebr**.\n- 2026-04-20: Fix merged to `develop`, `release/2.39`, and `release/2.33`.\n- 2026-04-29: 2.41.0 released.\n- 2026-05-07: 2.39.2-LTS and 2.33.8-LTS released.\n\n## Credit\n\n- **ikkebr** \u2014 identified and reported the proxy allowlist bypass affecting the Docker plugin management endpoints.",
  "id": "GHSA-rrmm-9v76-h3p4",
  "modified": "2026-05-14T16:22:50Z",
  "published": "2026-05-14T16:22:50Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/portainer/portainer/security/advisories/GHSA-rrmm-9v76-h3p4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/portainer/portainer"
    },
    {
      "type": "WEB",
      "url": "https://github.com/portainer/portainer/releases/tag/2.33.8"
    },
    {
      "type": "WEB",
      "url": "https://github.com/portainer/portainer/releases/tag/2.39.2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/portainer/portainer/releases/tag/2.41.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Portainer missing authorization on Docker plugin endpoints, which allows host RCE"
}


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…