CVE-2026-41232: Froxlor/froxlor Froxlor Has An Email Sender Vulnerability

MODERATE
Advisory Identifier: GHSA-vmjj-qr7v-pxm6
CVSS 5.0
CWE-863
Published
Advisory Summary

CVE-2026-41232 is a MODERATE (CVSS 5.0) froxlor has an email sender vulnerability (CWE-863: Incorrect Authorization) affecting Froxlor/froxlor libraries in the Packagist ecosystem. The issue involves froxlor has an email sender alias domain ownership bypass via wrong array index allows cross-customer email spoofing. Remediation is available by upgrading affected packages to version 2.3.6.

CVE-2026-41232 at a Glance

CVE Identifier CVE-2026-41232
GHSA Identifier GHSA-vmjj-qr7v-pxm6
Severity MODERATE
CVSS v3.1 Base Score 5.0
CWE Classification CWE-863: Incorrect Authorization
Affected Product Froxlor/froxlor
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 5.0

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality None (C:N)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2026-41232

To remediate this vulnerability, upgrade all affected Froxlor/froxlor dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

froxlor/froxlor fixed: 2.3.6
Upgrade froxlor/froxlor to 2.3.6
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for froxlor/froxlor

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-41232, inspect your project dependencies for Froxlor/froxlor:

inspect dependencies for froxlor/froxlor

Analyze the terminal output to locate any version of Froxlor/froxlor in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.3.6), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-863: Incorrect Authorization, where the product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. In Froxlor/froxlor, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary In `EmailSender::add()`, the domain ownership validation for full email sender aliases uses the wrong array index when splitting the email address, passing the local part instead of the domain to `validateLocalDomainOwnership()`. This causes the ownership check to always pass for non-existent "domains," allowing any authenticated customer to add sender aliases for email addresses on domains belonging to other customers. Postfix's `sender_login_maps` then authorizes the attacker to send emails as those addresses. ## Details In `lib/Froxlor/Api/Commands/EmailSender.php` at line 100, when a customer adds a full email address (not a `@domain` wildcard) as an allowed sender, the code splits on `@` and takes index `[0]`: ```php // Line 96-106 if (substr($allowed_sender, 0, 1) != '@') { if (!Validate::validateEmail($idna_convert->encode($allowed_sender))) { Response::standardError('emailiswrong', $allowed_sender, true); } self::validateLocalDomainOwnership(explode("@", $allowed_sender)[0] ?? ""); // BUG: [0] is the local part } else { if (!Validate::validateDomain($idna_convert->encode(substr($allowed_sender, 1)))) { Response::standardError('wildcardemailiswrong', substr($allowed_sender, 1), true); } self::validateLocalDomainOwnership(substr($allowed_sender, 1)); // CORRECT: passes domain } ``` For input `admin@domain-b.com`, `explode("@", "admin@domain-b.com")` returns `["admin", "domain-b.com"]`. Index `[0]` is `"admin"` — the local part, not the domain. The `validateLocalDomainOwnership()` function (lines 346-355) then queries `panel_domains` for a domain matching `"admin"`: ```php private static function validateLocalDomainOwnership(string $domain): void { $sel_stmt = Database::prepare("SELECT customerid FROM `" . TABLE_PANEL_DOMAINS . "` WHERE `domain` = :domain"); $domain_result = Database::pexecute_first($sel_stmt, ['domain' => $domain]); if ($domain_result && $domain_result['customerid'] != CurrentUser::getField('customerid')) { Response::standardError('senderdomainnotowned', $domain, true); } } ``` Since no domain named `"admin"` exists in `panel_domains`, `$domain_result` is false, and the function returns without error — the ownership check silently passes. The inserted `mail_sender_aliases` row is then picked up by Postfix's `sender_login_maps` query (configured in `mysql-virtual_sender_permissions.cf`): ```sql ... UNION (SELECT mail_sender_aliases.email FROM mail_sender_aliases WHERE mail_sender_aliases.allowed_sender = '%s') ... ``` This query maps the `allowed_sender` back to the mail user, authorizing them to send as that address via SMTP. ## PoC ```bash # Prerequisites: Froxlor instance with mail.enable_allow_sender enabled, # two customers: Customer A (owns domain-a.com) and Customer B (owns domain-b.com) # Step 1: As Customer A, add a sender alias claiming Customer B's domain # Via API: curl -X POST 'https://froxlor-host/api/v1/' \ -H 'Authorization: Basic <customer-A-credentials>' \ -H 'Content-Type: application/json' \ -d '{ "command": "EmailSender.add", "params": { "emailaddr": "myaccount@domain-a.com", "allowed_sender": "ceo@domain-b.com" } }' # Expected: Error "senderdomainnotowned" because domain-b.com belongs to Customer B # Actual: 200 OK — alias is created because validateLocalDomainOwnership # receives "ceo" (local part) instead of "domain-b.com" (domain) # Step 2: Verify the alias was inserted curl -X POST 'https://froxlor-host/api/v1/' \ -H 'Authorization: Basic <customer-A-credentials>' \ -H 'Content-Type: application/json' \ -d '{ "command": "EmailSender.listing", "params": {"emailaddr": "myaccount@domain-a.com"} }' # Step 3: Customer A can now send email as ceo@domain-b.com via SMTP # because Postfix sender_login_maps will match the mail_sender_aliases entry # and authorize Customer A's mail account to use that sender address. ``` The same attack works via the web UI by POST-ing to `customer_email.php` with `action=add_sender` and the target domain in `allowed_domain`. ## Impact Any authenticated customer on a multi-tenant Froxlor instance can add sender aliases for email addresses on domains belonging to other customers. This allows: - **Cross-customer email spoofing**: Send emails impersonating users on other customers' domains, bypassing Postfix's `smtpd_sender_login_maps` restriction that is specifically designed to prevent this. - **Multi-tenant isolation breach**: The domain ownership check (`validateLocalDomainOwnership`) is the only barrier preventing cross-customer sender aliasing, and it is completely ineffective for full email addresses. - **Phishing and reputation damage**: Spoofed emails originate from the legitimate mail server, passing SPF/DKIM checks for the target domain if those records point to the Froxlor server. Note: The wildcard (`@domain`) code path at line 105 is **not** affected — it correctly passes the domain to `validateLocalDomainOwnership()`. ## Recommended Fix Change index `[0]` to `[1]` on line 100 of `lib/Froxlor/Api/Commands/EmailSender.php`: ```php // Before (line 100): self::validateLocalDomainOwnership(explode("@", $allowed_sender)[0] ?? ""); // After: self::validateLocalDomainOwnership(explode("@", $allowed_sender)[1] ?? ""); ``` This ensures the domain part of the email address is passed to the ownership validation, matching the behavior of the wildcard path on line 105.

CVE-2026-41232 Affected Froxlor/froxlor Versions

1 component(s)
Fixed Versions:
2.3.6
Product / Package Ecosystem Affected Versions Fixed Version
froxlor/froxlor
Packagist
< 2.3.6
2.3.6

References

Frequently Asked Questions

What is CVE-2026-41232?
CVE-2026-41232 is a security vulnerability in Froxlor/froxlor involving Froxlor has an Email Sender Alias Domain Ownership Bypass via Wrong Array Index Allows Cross-Customer Email Spoofing under CWE-863: Incorrect Authorization.
Which Froxlor/froxlor versions are affected?
Affected versions include releases prior to 2.3.6 across affected packages: froxlor/froxlor.
What version fixes CVE-2026-41232?
Upgrading to 2.3.6 (or the latest patched release for your respective branch) resolves the vulnerability.
Is froxlor/froxlor affected?
Yes, froxlor/froxlor is affected in releases prior to 2.3.6. Upgrade your dependency to 2.3.6 or later.
What is the CVSS score of CVE-2026-41232?
CVE-2026-41232 has a CVSS v3.1 base score of 5.0 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N.
What CWE is associated with CVE-2026-41232?
CVE-2026-41232 is classified under CWE-863: Incorrect Authorization (The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.).
How can I check Packagist dependencies for CVE-2026-41232?
Run the dependency tree command: 'inspect dependencies for froxlor/froxlor'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-vmjj-qr7v-pxm6",
  "modified" : "2026-04-24T20:54:28Z",
  "published" : "2026-04-16T00:47:05Z",
  "aliases" : [ "CVE-2026-41232" ],
  "summary" : "Froxlor has an Email Sender Alias Domain Ownership Bypass via Wrong Array Index Allows Cross-Customer Email Spoofing",
  "details" : "## Summary\n\nIn `EmailSender::add()`, the domain ownership validation for full email sender aliases uses the wrong array index when splitting the email address, passing the local part instead of the domain to `validateLocalDomainOwnership()`. This causes the ownership check to always pass for non-existent \"domains,\" allowing any authenticated customer to add sender aliases for email addresses on domains belonging to other customers. Postfix's `sender_login_maps` then authorizes the attacker to send emails as those addresses.\n\n## Details\n\nIn `lib/Froxlor/Api/Commands/EmailSender.php` at line 100, when a customer adds a full email address (not a `@domain` wildcard) as an allowed sender, the code splits on `@` and takes index `[0]`:\n\n```php\n// Line 96-106\nif (substr($allowed_sender, 0, 1) != '@') {\n    if (!Validate::validateEmail($idna_convert->encode($allowed_sender))) {\n        Response::standardError('emailiswrong', $allowed_sender, true);\n    }\n    self::validateLocalDomainOwnership(explode(\"@\", $allowed_sender)[0] ?? \"\");  // BUG: [0] is the local part\n} else {\n    if (!Validate::validateDomain($idna_convert->encode(substr($allowed_sender, 1)))) {\n        Response::standardError('wildcardemailiswrong', substr($allowed_sender, 1), true);\n    }\n    self::validateLocalDomainOwnership(substr($allowed_sender, 1));  // CORRECT: passes domain\n}\n```\n\nFor input `admin@domain-b.com`, `explode(\"@\", \"admin@domain-b.com\")` returns `[\"admin\", \"domain-b.com\"]`. Index `[0]` is `\"admin\"` — the local part, not the domain.\n\nThe `validateLocalDomainOwnership()` function (lines 346-355) then queries `panel_domains` for a domain matching `\"admin\"`:\n\n```php\nprivate static function validateLocalDomainOwnership(string $domain): void\n{\n    $sel_stmt = Database::prepare(\"SELECT customerid FROM `\" . TABLE_PANEL_DOMAINS . \"` WHERE `domain` = :domain\");\n    $domain_result = Database::pexecute_first($sel_stmt, ['domain' => $domain]);\n    if ($domain_result && $domain_result['customerid'] != CurrentUser::getField('customerid')) {\n        Response::standardError('senderdomainnotowned', $domain, true);\n    }\n}\n```\n\nSince no domain named `\"admin\"` exists in `panel_domains`, `$domain_result` is false, and the function returns without error — the ownership check silently passes.\n\nThe inserted `mail_sender_aliases` row is then picked up by Postfix's `sender_login_maps` query (configured in `mysql-virtual_sender_permissions.cf`):\n\n```sql\n... UNION (SELECT mail_sender_aliases.email FROM mail_sender_aliases\nWHERE mail_sender_aliases.allowed_sender = '%s') ...\n```\n\nThis query maps the `allowed_sender` back to the mail user, authorizing them to send as that address via SMTP.\n\n## PoC\n\n```bash\n# Prerequisites: Froxlor instance with mail.enable_allow_sender enabled,\n# two customers: Customer A (owns domain-a.com) and Customer B (owns domain-b.com)\n\n# Step 1: As Customer A, add a sender alias claiming Customer B's domain\n# Via API:\ncurl -X POST 'https://froxlor-host/api/v1/' \\\n  -H 'Authorization: Basic <customer-A-credentials>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"command\": \"EmailSender.add\",\n    \"params\": {\n      \"emailaddr\": \"myaccount@domain-a.com\",\n      \"allowed_sender\": \"ceo@domain-b.com\"\n    }\n  }'\n\n# Expected: Error \"senderdomainnotowned\" because domain-b.com belongs to Customer B\n# Actual: 200 OK — alias is created because validateLocalDomainOwnership\n#         receives \"ceo\" (local part) instead of \"domain-b.com\" (domain)\n\n# Step 2: Verify the alias was inserted\ncurl -X POST 'https://froxlor-host/api/v1/' \\\n  -H 'Authorization: Basic <customer-A-credentials>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"command\": \"EmailSender.listing\",\n    \"params\": {\"emailaddr\": \"myaccount@domain-a.com\"}\n  }'\n\n# Step 3: Customer A can now send email as ceo@domain-b.com via SMTP\n# because Postfix sender_login_maps will match the mail_sender_aliases entry\n# and authorize Customer A's mail account to use that sender address.\n```\n\nThe same attack works via the web UI by POST-ing to `customer_email.php` with `action=add_sender` and the target domain in `allowed_domain`.\n\n## Impact\n\nAny authenticated customer on a multi-tenant Froxlor instance can add sender aliases for email addresses on domains belonging to other customers. This allows:\n\n- **Cross-customer email spoofing**: Send emails impersonating users on other customers' domains, bypassing Postfix's `smtpd_sender_login_maps` restriction that is specifically designed to prevent this.\n- **Multi-tenant isolation breach**: The domain ownership check (`validateLocalDomainOwnership`) is the only barrier preventing cross-customer sender aliasing, and it is completely ineffective for full email addresses.\n- **Phishing and reputation damage**: Spoofed emails originate from the legitimate mail server, passing SPF/DKIM checks for the target domain if those records point to the Froxlor server.\n\nNote: The wildcard (`@domain`) code path at line 105 is **not** affected — it correctly passes the domain to `validateLocalDomainOwnership()`.\n\n## Recommended Fix\n\nChange index `[0]` to `[1]` on line 100 of `lib/Froxlor/Api/Commands/EmailSender.php`:\n\n```php\n// Before (line 100):\nself::validateLocalDomainOwnership(explode(\"@\", $allowed_sender)[0] ?? \"\");\n\n// After:\nself::validateLocalDomainOwnership(explode(\"@\", $allowed_sender)[1] ?? \"\");\n```\n\nThis ensures the domain part of the email address is passed to the ownership validation, matching the behavior of the wildcard path on line 105.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "froxlor/froxlor"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.3.6"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/froxlor/froxlor/security/advisories/GHSA-vmjj-qr7v-pxm6"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-41232"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/froxlor/froxlor/commit/77d04badf549d5f8429828f0fbc69bc37a35e07a"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/froxlor/froxlor"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/froxlor/froxlor/releases/tag/2.3.6"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-863" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-16T00:47:05Z",
    "nvd_published_at" : "2026-04-23T05:16:05Z"
  }
}