Summary
modules/category-report/preferences.php performs persistent Category Report configuration changes from GET parameters without validating an adm_csrf_token. An attacker can cause an authenticated administrator to visit a crafted URL and delete or duplicate Category Report configurations.
Credit requested: Yaohui Wang
Affected repository / versions
Repository: https://github.com/Admidio/admidio
Observed in:
- master at e1fe6fd
- released tag v5.0.10
- released tag v5.0.9
Package: composer admidio/admidio
Vulnerable code paths
modules/category-report/preferences.php:
require_once(__DIR__ . '/../../system/login_valid.php');
if (!$gCurrentUser->isAdministrator()) {
throw new Exception('SYS_NO_RIGHTS');
}
$getAdd = admFuncVariableIsValid($_GET, 'add', 'bool');
$getDelete = admFuncVariableIsValid($_GET, 'delete', 'numeric', array('defaultValue' => 0));
$getCopy = admFuncVariableIsValid($_GET, 'copy', 'numeric', array('defaultValue' => 0));
if ($getDelete > 0 && isset($config[$getDelete - 1])) {
$config[$getDelete - 1]['id'] = $config[$getDelete - 1]['id'] * (-1);
$config = $report->saveConfigArray($config);
}
if ($getCopy > 0) {
$config[] = array(...);
$config = $report->saveConfigArray($config);
}
There is no call to SecurityUtils::validateCsrfToken() and no form-object CSRF validation before the state-changing delete and copy branches.
system/classes/CategoryReport.php confirms that saveConfigArray() persists these changes:
public function saveConfigArray(array $arrConfiguration): array
{
$gDb->startTransaction();
foreach ($arrConfiguration as $values) {
if ($values['id'] === '' || $values['id'] > 0) {
$categoryReport = new Entity($gDb, TBL_CATEGORY_REPORT, 'crt', $values['id']);
...
$categoryReport->save();
$gSettingsManager->set('category_report_default_configuration', $defaultConfiguration);
} else {
$values['id'] = $values['id'] * (-1);
$categoryReport = new Entity($gDb, TBL_CATEGORY_REPORT, 'crt', $values['id']);
$categoryReport->delete();
}
}
$gDb->endTransaction();
}
Impact
An attacker who can get an authenticated administrator to perform a top-level navigation can trigger unauthorized Category Report configuration changes:
GET /modules/category-report/preferences.php?delete=1 deletes the first persisted configuration if present.
GET /modules/category-report/preferences.php?copy=1 duplicates the first persisted configuration if present.
This affects integrity and availability of the Category Report module configuration. The server-side delete branch does not require the UI-generated delete link and does not enforce the UI limitation that only non-default configurations are shown with a delete button; direct requests are accepted if the referenced index exists.
Admidio session cookies are configured with SameSite=Lax (src/Session/Entity/Session.php), which still allows cookies on top-level cross-site GET navigations. Therefore, a normal link/form/window navigation is sufficient for CSRF; a token is still needed for these state-changing actions.
Minimal proof of concept
Prerequisites:
- Category Report module is enabled.
- The victim is logged in as an Admidio administrator.
- At least one Category Report configuration exists.
A malicious page can trigger the delete action with a top-level navigation:
<!doctype html>
<html>
<body>
<a href="https://victim.example/modules/category-report/preferences.php?delete=1">View report</a>
<script>
// User interaction / top-level navigation variant:
// location.href = "https://victim.example/modules/category-report/preferences.php?delete=1";
</script>
</body>
</html>
Expected result when visited by an authenticated administrator: preferences.php enters the $getDelete branch and CategoryReport::saveConfigArray() deletes the selected row from adm_category_report without any CSRF token.
A copy/clutter variant is also possible:
<a href="https://victim.example/modules/category-report/preferences.php?copy=1">View report</a>
Suggested fix
Move the persistent delete and copy operations behind POST-only handlers and require adm_csrf_token validation before calling CategoryReport::saveConfigArray(). Alternatively, require SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']) and reject GET for all persistent configuration changes in modules/category-report/preferences.php.
It would also be safer to enforce the server-side business rule that default / last remaining configurations cannot be deleted, instead of relying only on whether the UI renders a delete URL.
Suggested classification
CWE-352: Cross-Site Request Forgery (CSRF)
Suggested severity: Medium, although Low is also defensible depending on whether the authenticated administrator victim is modeled as PR:H. The vulnerability requires admin login state and user interaction, but it performs persistent configuration changes without token validation.
Summary
modules/category-report/preferences.phpperforms persistent Category Report configuration changes from GET parameters without validating anadm_csrf_token. An attacker can cause an authenticated administrator to visit a crafted URL and delete or duplicate Category Report configurations.Credit requested: Yaohui Wang
Affected repository / versions
Repository: https://github.com/Admidio/admidio
Observed in:
Package: composer
admidio/admidioVulnerable code paths
modules/category-report/preferences.php:There is no call to
SecurityUtils::validateCsrfToken()and no form-object CSRF validation before the state-changingdeleteandcopybranches.system/classes/CategoryReport.phpconfirms thatsaveConfigArray()persists these changes:Impact
An attacker who can get an authenticated administrator to perform a top-level navigation can trigger unauthorized Category Report configuration changes:
GET /modules/category-report/preferences.php?delete=1deletes the first persisted configuration if present.GET /modules/category-report/preferences.php?copy=1duplicates the first persisted configuration if present.This affects integrity and availability of the Category Report module configuration. The server-side
deletebranch does not require the UI-generated delete link and does not enforce the UI limitation that only non-default configurations are shown with a delete button; direct requests are accepted if the referenced index exists.Admidio session cookies are configured with
SameSite=Lax(src/Session/Entity/Session.php), which still allows cookies on top-level cross-site GET navigations. Therefore, a normal link/form/window navigation is sufficient for CSRF; a token is still needed for these state-changing actions.Minimal proof of concept
Prerequisites:
A malicious page can trigger the delete action with a top-level navigation:
Expected result when visited by an authenticated administrator:
preferences.phpenters the$getDeletebranch andCategoryReport::saveConfigArray()deletes the selected row fromadm_category_reportwithout any CSRF token.A copy/clutter variant is also possible:
Suggested fix
Move the persistent
deleteandcopyoperations behind POST-only handlers and requireadm_csrf_tokenvalidation before callingCategoryReport::saveConfigArray(). Alternatively, requireSecurityUtils::validateCsrfToken($_POST['adm_csrf_token'])and reject GET for all persistent configuration changes inmodules/category-report/preferences.php.It would also be safer to enforce the server-side business rule that default / last remaining configurations cannot be deleted, instead of relying only on whether the UI renders a delete URL.
Suggested classification
CWE-352: Cross-Site Request Forgery (CSRF)
Suggested severity: Medium, although Low is also defensible depending on whether the authenticated administrator victim is modeled as
PR:H. The vulnerability requires admin login state and user interaction, but it performs persistent configuration changes without token validation.