Root cause
Authentication & access control introduced
new: [event-template(io)] Add EventTemplateExporter and EventTemplateImporter
How AI contributed: Direct introductionPublished Jun 4, 2026 in
misp/mispAll 2 findings in this repository
An authorization flaw existed in the MISP Event Template Importer overwrite workflow. When importing an event template in overwrite mode, the application checked whether a matching template already existed but did not verify that the importing user belonged to the organization that owned the existing template. As a result, an authenticated user with access to the template import functionality could forcibly overwrite an event template owned by another organization. Successful exploitation could allow unauthorized modification of another organization’s event template, potentially altering template structure, attributes, or metadata used for subsequent event creation or sharing workflows. Site administrators are not affected by this restriction, as they are explicitly allowed to overwrite templates across organizations. The issue was fixed by enforcing an ownership check before overwrite: non-site-admin users may only overwrite templates owned by their own organization.
CIRCL CVE-2026-10855 names Event Template Importer overwrite without org check and patch 7c2200d1. Unreviewed GHSA-243v-5f97-vfq3 has cve_id=CVE-2026-10855. Parent of 41450bdb has no EventTemplateImporter.php. Member 41450bdb (Claude Opus 4.7) adds overwrite that preserves org_id/creator and states auth is the caller's responsibility. Controller 0efb4e00a (also Claude) POSTs mode=overwrite into that lib with only perm_template ACL. Fix 7c2200d1 adds org_id!==existing.org_id && !perm_site_admin throw. v2.5.36 lacks candidate. Published GitHub v2.5.37/v2.5.38 contain 41450bdb without the fix and still lack the org throw. v2.5.39 contains 7c2200d1.
Root cause
new: [event-template(io)] Add EventTemplateExporter and EventTemplateImporter
Fix
fix: [security] event template importer - check for ownership on overwrite forced imports
Case facts
--- /dev/null+++ b/app/Lib/Tools/EventTemplateImporter.php@@ -0,0 +1,194 @@+<?php+App::uses('EventTemplateDependencies', 'Tools');+App::uses('EventTemplateValidator', 'Tools');+App::uses('EventTemplateImportException', 'Tools');+App::uses('CakeText', 'Utility');+App::uses('ClassRegistry', 'Utility');++/**+ * Consumes an export document (as produced by EventTemplateExporter) and+ * writes it into this MISP instance as a new or overwritten EventTemplate+ * row. Validates the envelope and runs the full structural + semantic+ * validator so import never stores a template that save() would have+ * rejected.+ *+ * Auth is the caller's responsibility — this lib is permission-agnostic+ * and does not check perm_template or org boundaries; the Phase 1.5+ * controller does that before calling in.+ *+ * Failure modes for uuid collisions are selectable via+ * $options['mode']:+ * 'fail' (default) throw if a template with that uuid+ * already exists+ * 'overwrite' update the existing row; its creator_user_id+ * and org_id are preserved+ * 'duplicate_as_new' generate a fresh uuid and save as a new row+ * owned by the importing user/org+ */+class EventTemplateImporter+{+ const SCHEMA_VERSION = 1;++ /**+ * @param array $payload document as produced by EventTemplateExporter+ * @param array $user CakePHP Auth user (must have id + org_id at minimum)+ * @param array $options ['mode' => 'fail' | 'overwrite' | 'duplicate_as_new']+ * @return array ['event_template_id' => int, 'event_template_uuid' => string, 'mode' => string]+ * @throws EventTemplateImportException+ * @throws EventTemplateDependencyMissingException+ */+ public function import(array $payload, array $user, array $options = array())+ {+ EventTemplateDependencies::requireAll();++ $mode = isset($options['mode']) ? (string)$options['mode'] : 'fail';+ if (!in_array($mode, array('fail', 'overwrite', 'duplicate_as_new'), true)) {+ throw new EventTemplateImportException(+ 'Invalid import mode.',+ array(sprintf('unknown mode "%s"; valid: fail, overwrite, duplicate_as_new', $mode))+ );+ }++ $envErrs = $this->validateEnvelope($payload);+ if (!empty($envErrs)) {+ throw new EventTemplateImportException('Import envelope is invalid.', $envErrs);+ }++ $template = $payload['template'];+ $definition = $template['definition'];++ $validator = new EventTemplateValidator();+ $defErrs = $validator->validate($definition);+ if (!empty($defErrs)) {+ throw new EventTemplateImportException(+ 'Template definition in import is invalid.',+ $defErrs+ );+ }++ $eventTemplate = ClassRegistry::init('EventTemplate');+ $existing = null;+ if (!empty($template['uuid'])) {+ $existing = $eventTemplate->find('first', array(+ 'conditions' => array('EventTemplate.uuid' => strtolower($template['uuid'])),+ 'recursive' => -1,+ ));+ }++ if (!empty($existing) && $mode === 'fail') {+ throw new EventTemplateImportException(+ 'Template with this uuid already exists on this instance.',+ array(sprintf(+ 'uuid %s already exists (id=%d); retry with mode=overwrite or mode=duplicate_as_new',+ $template['uuid'],+ (int)$existing['EventTemplate']['id']+ ))+ );+ }++ $row = array(+ 'name' => (string)$template['name'],+ 'description' => isset($template['description']) ? $template['description'] : null,+ 'distribution' => isset($template['distribution']) ? (int)$template['distribution'] : 0,+ 'active' => !isset($template['active']) ? 1 : ($template['active'] ? 1 : 0),+ 'definition' => $definition,+ );++ if (!empty($existing) && $mode === 'overwrite') {+ $eventTemplate->id = (int)$existing['EventTemplate']['id'];+ $row['id'] = $eventTemplate->id;+ // preserve original ownership+ $row['uuid'] = $existing['EventTemplate']['uuid'];+ $row['org_id'] = (int)$existing['EventTemplate']['org_id'];+ $row['creator_user_id'] = (int)$existing['EventTemplate']['creator_user_id'];+ } else {+ $eventTemplate->create();+ if (!empty($existing) && $mode === 'duplicate_as_new') {+ $row['uuid'] = strtolower(CakeText::uuid());+ } else {+ $row['uuid'] = strtolower((string)$template['uuid']);+ }+ $row['org_id'] = isset($user['org_id']) ? (int)$user['org_id'] : 0;+ $row['creator_user_id'] = isset($user['id']) ? (int)$user['id'] : 0;+ }++ $ok = $eventTemplate->save(array('EventTemplate' => $row));+ if (!$ok) {+ throw new EventTemplateImportException(--- a/app/Lib/Tools/EventTemplateImporter.php+++ b/app/Lib/Tools/EventTemplateImporter.php@@ -110,6 +110,9 @@ class EventTemplateImporter ); if (!empty($existing) && $mode === 'overwrite') {+ if ($user['org_id'] !== (int)$existing['EventTemplate']['org_id'] && !$user['Role']['perm_site_admin']) {+ throw new MethodNotAllowedException('Cannot overwrite template owned by another org.');+ } $eventTemplate->id = (int)$existing['EventTemplate']['id']; $row['id'] = $eventTemplate->id; // preserve original ownershipAI-assisted change 988a8ea434f08e2c3d7238233437ed631239660093affa0e3fe592a90e05b51e · Fix 092d5e0af4ec4f02d6b12170e0821e6f752f2c30f223407bc40b91f6ad412cfc