GitHub Security Advisory: GHSA-47hf-23pw-3m8c
Package: froxlor/froxlor (COMPOSER)
Severity: HIGH (CVSS 8.5)
CWE: CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
Vulnerable versions: < 2.3.6
Patched in: 2.3.6

Summary: Froxlor has a BIND Zone File Injection via Unsanitized DNS Record Content in DomainZones::add()

Details:
## Summary

`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.

## Details

**Missing type whitelist — `DomainZones.php:93`:**

The `type` parameter is accepted directly from user input with no validation against allowed values:

```php
// lib/Froxlor/Api/Commands/DomainZones.php:93
$type = $this->getParam('type', true, 'A');
```

The if/elseif chain at lines 170-317 validates content only for 13 known types: `A`, `AAAA`, `CAA`, `CNAME`, `DNAME`, `LOC`, `MX`, `NS`, `RP`, `SRV`, `SSHFP`, `TLSA`, `TXT`. Any type not in this list falls through with no content validation at all. There is a `TODO` comment at line 148 acknowledging missing validation:

```php
// TODO regex validate content for invalid characters
```

**Missing newline sanitization — `DomainZones.php:154`:**

The content field only receives `trim()`, which strips leading/trailing whitespace but preserves embedded newline characters:

```php
// lib/Froxlor/Api/Commands/DomainZones.php:154
$content = trim($content);
```

**Unsafe zone file output — `DnsEntry.php:83`:**

`DnsEntry::__toString()` concatenates content directly into zone file format without escaping:

```php
// lib/Froxlor/Dns/DnsEntry.php:83
return $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t"
    . (($this->priority >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->priority . "\t" : "")
    . $_content . PHP_EOL;
```

Newlines in `$_content` produce new lines in the zone file, each parsed by BIND as an independent resource record or directive.

**Zone file write — `Bind.php:121`:**

```php
// lib/Froxlor/Cron/Dns/Bind.php:121
fwrite($zonefile_handler, $zoneContent . $subzones);
```

The `AntiXSS` filter applied at the API layer (`Api.php:91`) targets HTML/JS XSS vectors and does not strip newline characters. The web UI form restricts types via a dropdown (`formfield.dns_add.php:42-56`), but this is client-side only — the server-side `DomainZones::add()` has no corresponding whitelist.

**Execution flow:**
1. Customer sends API request with `type=NAPTR` and `content` containing `\n`-separated lines
2. `getParam()` returns raw values without sanitization
3. Type `NAPTR` matches none of the if/elseif conditions — no content validation runs
4. `trim($content)` preserves embedded newlines
5. Content is inserted into `domain_dns` table via prepared statement
6. DNS cron creates `DnsEntry` objects from DB records (`Dns.php:297`)