CVE-2026-41230
Description
Froxlor is open source server administration software. Prior to version 2.3.6, DomainZones::add() accepts arbitrary DNS record types without a whitelist and does not sanitize newline characters in the content field. When a DNS type not covered by the if/elseif validation chain is submitted (e.g., NAPTR, PTR, HINFO), content validation is entirely bypassed. Embedded newline characters in the content survive trim() processing, are stored in the database, and are written directly into BIND zone files via DnsEntry::__toString(). An authenticated customer can inject arbitrary DNS records and BIND directives ($INCLUDE, $ORIGIN, $GENERATE) into their domain's zone file. Version 2.3.6 fixes the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
froxlor/froxlorPackagist | < 2.3.6 | 2.3.6 |
Affected products
1Patches
147a8af5d9523add validation for DNS NAPTR record content
4 files changed · +61 −15
lib/Froxlor/Api/Commands/DomainZones.php+4 −12 modified@@ -213,9 +213,6 @@ public function add() } elseif ($type == 'LOC' && !empty($content)) { if (!Validate::validateDnsLoc($content)) { $errors[] = lng('error.dns_loc_invalid'); - } else { - // keep content - $content = $content; } } elseif ($type == 'MX') { if ($prio === null || $prio < 0) { @@ -244,6 +241,10 @@ public function add() if ($content == '.' && $prio != 0) { $prio = 0; } + } elseif ($type == 'NAPTR' && !empty($content)) { + if (!Validate::validateDnsNaptr($content)) { + $errors[] = lng('error.dns_naptr_invalid'); + } } elseif ($type == 'NS') { // check for trailing dot if (substr($content, -1) == '.') { @@ -258,9 +259,6 @@ public function add() } elseif ($type == 'RP' && !empty($content)) { if (!Validate::validateDnsRp($content)) { $errors[] = lng('error.dns_rp_invalid'); - } else { - // keep content - $content = $content; } } elseif ($type == 'SRV') { if ($prio === null || $prio < 0) { @@ -300,16 +298,10 @@ public function add() } elseif ($type == 'SSHFP' && !empty($content)) { if (!Validate::validateDnsSshfp($content)) { $errors[] = lng('error.dns_sshfp_invalid'); - } else { - // keep content - $content = $content; } } elseif ($type == 'TLSA' && !empty($content)) { if (!Validate::validateDnsTlsa($content)) { $errors[] = lng('error.dns_tlsa_invalid'); - } else { - // keep content - $content = $content; } } elseif ($type == 'TXT' && !empty($content)) { // check that TXT content is enclosed in " "
lib/Froxlor/Validate/Validate.php+55 −3 modified@@ -62,8 +62,8 @@ public static function validate( string $str, string $fieldname, string $pattern = '', - $lng = '', - $emptydefault = [], + $lng = '', + $emptydefault = [], bool $throw_exception = false ) { @@ -440,7 +440,7 @@ public static function validateDnsRp(string $input) // remove trailing dot if any $mboxDname = rtrim($mboxDname, '.'); - $txtDname = rtrim($txtDname, '.'); + $txtDname = rtrim($txtDname, '.'); if (!self::validateDomain($mboxDname)) { return false; @@ -557,4 +557,56 @@ public static function validateDnsTlsa(string $input) return $input; } + + public static function validateDnsNaptr(string $input): bool + { + // Split respecting quoted strings + $pattern = '/^ + (\d{1,5})\s+ # order + (\d{1,5})\s+ # preference + "([^"]*)"\s+ # flags + "([^"]*)"\s+ # services + "([^"]*)"\s+ # regexp + (\S+) # replacement + $/x'; + + if (!preg_match($pattern, $input, $matches)) { + return false; + } + + [, $order, $preference, $flags, $services, $regexp, $replacement] = $matches; + + // 1. order & preference: 0–65535 + if ($order < 0 || $order > 65535 || $preference < 0 || $preference > 65535) { + return false; + } + + // 2. flags: allowed chars (RFC says single letters typically, but allow multiple) + if (!preg_match('/^[A-Za-z0-9]*$/', $flags)) { + return false; + } + + // 3. services: usually like "E2U+sip" + if (!preg_match('/^[A-Za-z0-9+:\-]*$/', $services)) { + return false; + } + + // 4. regexp: delimiter-based substitution (very loose validation) + // Example: !^.*$!sip:info@example.com! + if ($regexp !== '') { + $delim = $regexp[0]; + if (substr_count($regexp, $delim) < 3) { + return false; + } + } + + // 5. replacement: must be "." or valid domain + if ($replacement !== '.') { + if (!filter_var($replacement, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) { + return false; + } + } + + return true; + } }
lng/de.lng.php+1 −0 modified@@ -981,6 +981,7 @@ 'dns_rp_invalid' => 'Ungültiger RP Eintrag', 'dns_sshfp_invalid' => 'Ungültiger SSHFP Eintrag', 'dns_tlsa_invalid' => 'Ungültiger TLSA Eintrag', + 'dns_naptr_invalid' => 'Ungültiger NAPTR Eintrag', 'domain_nopunycode' => 'Die Eingabe von Punycode (IDNA) ist nicht notwendig. Die Domain wird automatisch konvertiert.', 'domain_noipaddress' => 'Eine IP-Adresse kann nicht als Domain angelegt werden', 'dns_record_toolong' => 'Records/Labels können maximal 63 Zeichen lang sein',
lng/en.lng.php+1 −0 modified@@ -1052,6 +1052,7 @@ 'dns_rp_invalid' => 'The RP record has invalid content', 'dns_sshfp_invalid' => 'The SSHFP record has invalid content', 'dns_tlsa_invalid' => 'The TLSA record has invalid content', + 'dns_naptr_invalid' => 'The NAPTR record has invalid content', 'domain_nopunycode' => 'You must not specify punycode (IDNA). The domain will automatically be converted', 'domain_noipaddress' => 'Cannot add an IP address as domain', 'dns_record_toolong' => 'Records/labels can only be up to 63 characters',
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/froxlor/froxlor/commit/47a8af5d9523cb6ec94567405cfc2e294d3a1442nvdPatchWEB
- github.com/froxlor/froxlor/security/advisories/GHSA-47hf-23pw-3m8cnvdExploitVendor AdvisoryMitigationWEB
- github.com/advisories/GHSA-47hf-23pw-3m8cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-41230ghsaADVISORY
- github.com/froxlor/froxlor/releases/tag/2.3.6nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.